From 30420b8c1fc74bdd8e050b971e669885f42cf9c3 Mon Sep 17 00:00:00 2001 From: CrystalSplitter Date: Sat, 24 Oct 2026 15:47:52 -0700 Subject: [PATCH 1/3] Put AnimationMotionPref/ThumbnailTypePref in Redux This saves us costly IPC calls on each component render. Additionally, move the getters to invoke, so we can't slow down the world with `sendSync`. --- src/bridges/settings.ts | 16 ++++---- src/components/cards/cached-img.tsx | 34 ++++++++-------- src/components/cards/thumbnail.tsx | 10 ++++- src/components/settings/app.tsx | 38 ++++++++++------- src/main/settings.ts | 11 ++--- src/scripts/models/app.ts | 63 ++++++++++++++++++++++++++--- src/scripts/settings.ts | 12 +++--- 7 files changed, 124 insertions(+), 60 deletions(-) diff --git a/src/bridges/settings.ts b/src/bridges/settings.ts index 0c710b95..905d0d58 100644 --- a/src/bridges/settings.ts +++ b/src/bridges/settings.ts @@ -53,11 +53,11 @@ const settingsBridge = { }); }, - getAnimationMotionPref: (): AnimationMotionPref => { - return ipcRenderer.sendSync("get-animation-motion-pref"); + getAnimationMotionPref: (): Promise => { + return ipcRenderer.invoke("get-animation-motion-pref"); }, - setAnimationMotionPref: (pref: AnimationMotionPref) => { - ipcRenderer.invoke("set-animation-motion-pref", pref); + setAnimationMotionPref: (pref: AnimationMotionPref): Promise => { + return ipcRenderer.invoke("set-animation-motion-pref", pref); }, getNativeWindowFramePref: (): boolean => { @@ -67,11 +67,11 @@ const settingsBridge = { ipcRenderer.invoke("set-window-native-frame-pref", pref); }, - getThumbnailTypePref: (): ThumbnailTypePref => { - return ipcRenderer.sendSync("get-thumbnail-type-pref"); + getThumbnailTypePref: (): Promise => { + return ipcRenderer.invoke("get-thumbnail-type-pref"); }, - setThumbnailTypePref: (pref: ThumbnailTypePref) => { - ipcRenderer.invoke("set-thumbnail-type-pref", pref); + setThumbnailTypePref: (pref: ThumbnailTypePref): Promise => { + return ipcRenderer.invoke("set-thumbnail-type-pref", pref); }, setLocaleSettings: (option: string) => { diff --git a/src/components/cards/cached-img.tsx b/src/components/cards/cached-img.tsx index 2c022065..94a98fab 100644 --- a/src/components/cards/cached-img.tsx +++ b/src/components/cards/cached-img.tsx @@ -24,10 +24,7 @@ class CachedImg extends React.Component { private readonly _canvasRef = React.createRef(); private _needsRescaling: boolean = true; private _imgSource: - | HTMLImageElement - | HTMLVideoElement - | VideoFrame[] - | null = null; + HTMLImageElement | HTMLVideoElement | VideoFrame[] | null = null; private _animationTimeout: NodeJS.Timeout | null = null; private _requestVideoFrameCallback: number | null = null; private _maxCanvasWidth: number; @@ -143,14 +140,15 @@ class CachedImg extends React.Component { } private async renderImage(): Promise { - const animationMotionPref = getAnimationMotionPref(); - const realisedAnimationMotionPref = - animationMotionPref !== AnimationMotionPref.System - ? animationMotionPref - : window.utils.systemPreferencesGetAnimationSettings() - .prefersReducedMotion - ? AnimationMotionPref.Off - : AnimationMotionPref.On; + const realisedAmpPromise = getAnimationMotionPref().then( + (animationMotionPref) => + animationMotionPref !== AnimationMotionPref.System + ? animationMotionPref + : window.utils.systemPreferencesGetAnimationSettings() + .prefersReducedMotion + ? AnimationMotionPref.Off + : AnimationMotionPref.On, + ); if (CachedImg._cache.has(this.props.src)) { const imgSource = CachedImg._cache.get(this.props.src); if (imgSource === placeholderImageSource) { @@ -223,15 +221,17 @@ class CachedImg extends React.Component { ); this._requestVideoFrameCallback = null; } - const requestFrame = () => { + const requestFrame = async () => { this.draw(video); + const realisedAnimationMotionPref = + await realisedAmpPromise; if (realisedAnimationMotionPref === AnimationMotionPref.On) this._requestVideoFrameCallback = video.requestVideoFrameCallback(requestFrame); }; if (video.readyState < video.HAVE_CURRENT_DATA) video.addEventListener("loadeddata", requestFrame); - else requestFrame(); + else await requestFrame(); } else { const frames = this._imgSource; let frameIndex = 0; @@ -239,11 +239,13 @@ class CachedImg extends React.Component { clearTimeout(this._animationTimeout); this._animationTimeout = null; } - const drawFrame = () => { + const drawFrame = async () => { if (frames.length === 0) return; const frame = frames[frameIndex]; const duration = frame.duration / 1000; this.draw(frame); + const realisedAnimationMotionPref = + await realisedAmpPromise; if ( frames.length > 1 && realisedAnimationMotionPref === AnimationMotionPref.On @@ -255,7 +257,7 @@ class CachedImg extends React.Component { ); } }; - drawFrame(); + await drawFrame(); } } } diff --git a/src/components/cards/thumbnail.tsx b/src/components/cards/thumbnail.tsx index 876725c8..670426f1 100644 --- a/src/components/cards/thumbnail.tsx +++ b/src/components/cards/thumbnail.tsx @@ -1,7 +1,8 @@ import * as React from "react"; import { RSSItem } from "../../scripts/models/item"; import CachedImg from "./cached-img"; -import { getThumbnailTypePref } from "../../scripts/settings"; +import { type ThumbnailTypePref } from "../../schema-types"; +import { type RootState, useAppSelector } from "../../scripts/reducer"; type CardThumbnailProps = { item: RSSItem; @@ -25,8 +26,13 @@ const mediaElement = ( ); }; +/** Selector for the thumbnail preference. */ +function useThumbnailTypePref(state: RootState): ThumbnailTypePref { + return state.app.thumbnailTypePref; +} + function CardThumbnail(props: CardThumbnailProps): React.JSX.Element { - const preferredThumbnailType = getThumbnailTypePref(); + const preferredThumbnailType = useAppSelector(useThumbnailTypePref); const preferredThumbnail = props.item.thumbnails?.filter( (t) => t.type === preferredThumbnailType, )?.[0]; diff --git a/src/components/settings/app.tsx b/src/components/settings/app.tsx index b58485aa..43118963 100644 --- a/src/components/settings/app.tsx +++ b/src/components/settings/app.tsx @@ -18,15 +18,16 @@ import { getThemeSettings, setThemeSettings, getAnimationMotionPref, - setAnimationMotionPref, setDefaultOpenTargetPref, getNativeWindowFramePref, setNativeWindowFramePref, exportAll, - getThumbnailTypePref, - setThumbnailTypePref, } from "../../scripts/settings"; -import { SET_DEFAULT_OPEN_TARGET } from "../../scripts/models/app"; +import { + SET_DEFAULT_OPEN_TARGET, + setThumbnailPref, + setAnimationPref, +} from "../../scripts/models/app"; import { ChoiceGroup, DefaultButton, @@ -382,13 +383,18 @@ function PacSettings() { ); } +/** Selector for the animation preference. */ +function useAnimationMotionPref(state: RootState): AnimationMotionPref { + return state.app.animationMotionPref; +} + /** * React component for animation preference dropdown. */ function AnimationPreferences(): React.JSX.Element { - const [animationProp, setAnimationProp] = React.useState( - getAnimationMotionPref(), - ); + const dispatch = useAppDispatch(); + const animationPref = useAppSelector(useAnimationMotionPref); + const preferenceOptions: IDropdownOption[] = [ { key: AnimationMotionPref.System, @@ -408,8 +414,7 @@ function AnimationPreferences(): React.JSX.Element { }, ]; const prefChange = (_: any, item: IDropdownOption) => { - setAnimationMotionPref(item.key as AnimationMotionPref); - setAnimationProp(item.key as AnimationMotionPref); + dispatch(setAnimationPref(item.key as AnimationMotionPref)); }; return ( @@ -421,7 +426,7 @@ function AnimationPreferences(): React.JSX.Element { @@ -459,10 +464,14 @@ function NativeWindowFramePreference(): React.JSX.Element { ); } +/** Selector for the thumbnail preference. */ +function useThumbnailTypePref(state: RootState): ThumbnailTypePref { + return state.app.thumbnailTypePref; +} + function ThumbnailTypePreferences() { - const [thumbnailType, setThumbnailType] = React.useState( - getThumbnailTypePref(), - ); + const dispatch = useAppDispatch(); + const thumbnailType = useAppSelector(useThumbnailTypePref); const preferenceOptions: IDropdownOption[] = [ { key: ThumbnailTypePref.OpenGraph, @@ -484,8 +493,7 @@ function ThumbnailTypePreferences() { }, ]; const prefChange = (_: any, item: IDropdownOption) => { - setThumbnailTypePref(item.key as ThumbnailTypePref); - setThumbnailType(item.key as ThumbnailTypePref); + dispatch(setThumbnailPref(item.key as ThumbnailTypePref)); }; return ( diff --git a/src/main/settings.ts b/src/main/settings.ts index 30dce935..91bd5543 100644 --- a/src/main/settings.ts +++ b/src/main/settings.ts @@ -104,8 +104,8 @@ export function setThemeListener(manager: WindowManager) { } const ANIMATION_MOTION_PREF_KEY = "animationMotionPref"; -ipcMain.on("get-animation-motion-pref", (event) => { - event.returnValue = getStore().get( +ipcMain.handle("get-animation-motion-pref", () => { + return getStore().get( ANIMATION_MOTION_PREF_KEY, AnimationMotionPref.System, ); @@ -126,11 +126,8 @@ ipcMain.handle("set-window-native-frame-pref", (_, pref: boolean) => { }); const THUMBNAIL_TYPE_PREF = "thumbnailTypePref"; -ipcMain.on("get-thumbnail-type-pref", (event) => { - event.returnValue = getStore().get( - THUMBNAIL_TYPE_PREF, - ThumbnailTypePref.OpenGraph, - ); +ipcMain.handle("get-thumbnail-type-pref", () => { + return getStore().get(THUMBNAIL_TYPE_PREF, ThumbnailTypePref.OpenGraph); }); ipcMain.handle("set-thumbnail-type-pref", (_, pref: ThumbnailTypePref) => { getStore().set(THUMBNAIL_TYPE_PREF, pref); diff --git a/src/scripts/models/app.ts b/src/scripts/models/app.ts index 8aca0d00..0dd8a41e 100644 --- a/src/scripts/models/app.ts +++ b/src/scripts/models/app.ts @@ -30,10 +30,16 @@ import { getCurrentLocale, setThemeDefaultFont, hasWindowFrame, + setAnimationMotionPref, + setThumbnailTypePref, } from "../settings"; import locales from "../i18n/_locales"; import { SYNC_SERVICE, ServiceActionTypes } from "./service"; -import { SourceOpenTarget } from "../../schema-types"; +import { + AnimationMotionPref, + SourceOpenTarget, + ThumbnailTypePref, +} from "../../schema-types"; export const enum ContextMenuType { Hidden, @@ -85,7 +91,11 @@ export class AppState { menu = getWindowBreakpoint() && window.settings.getDefaultMenu(); menuKey = ALL; title = ""; + defaultOpenTarget = SourceOpenTarget.Local; + animationMotionPref = AnimationMotionPref.System; + thumbnailTypePref = ThumbnailTypePref.OpenGraph; + addSourceModal = { display: false, }; @@ -196,6 +206,8 @@ export const SET_SETTINGS_TAB = "SET_SETTINGS_TAB"; export const SAVE_SETTINGS = "SAVE_SETTINGS"; export const FREE_MEMORY = "FREE_MEMORY"; export const SET_DEFAULT_OPEN_TARGET = "SET_DEFAULT_OPEN_TARGET"; +export const SET_THUMBNAIL_TYPE_PREF = "SET_THUMBNAIL_TYPE_PREF"; +export const SET_ANIMATION_MOTION_PREF = "SET_ANIMATION_MOTION_PREF"; interface ToggleSettingsAction { type: typeof TOGGLE_SETTINGS; @@ -218,12 +230,22 @@ interface SetDefaultOpenTargetAction { type: typeof SET_DEFAULT_OPEN_TARGET; value: SourceOpenTarget; } +interface SetThumbnailTypePref { + type: typeof SET_THUMBNAIL_TYPE_PREF; + value: ThumbnailTypePref; +} +interface SetAnimationMotionPref { + type: typeof SET_ANIMATION_MOTION_PREF; + value: AnimationMotionPref; +} export type SettingsActionTypes = - | ToggleSettingsAction + | FreeMemoryAction + | SaveSettingsAction + | SetAnimationMotionPref | SetDefaultOpenTargetAction | SetSettingsTabAction - | SaveSettingsAction - | FreeMemoryAction; + | SetThumbnailTypePref + | ToggleSettingsAction; const SHOW_ADD_SOURCE_MODAL = "SHOW_ADD_SOURCE_MODAL"; const HIDE_ADD_SOURCE_MODAL = "HIDE_ADD_SOURCE_MODAL"; @@ -236,8 +258,7 @@ interface HideAddSourceModalAction { } export type AddSourceModalActionTypes = - | ShowAddSourceModalAction - | HideAddSourceModalAction; + ShowAddSourceModalAction | HideAddSourceModalAction; export function showAddSourceModal(): ShowAddSourceModalAction { return { type: SHOW_ADD_SOURCE_MODAL }; @@ -247,6 +268,20 @@ export function hideAddSourceModal(): HideAddSourceModalAction { return { type: HIDE_ADD_SOURCE_MODAL }; } +export function setThumbnailPref( + pref: ThumbnailTypePref, +): SetThumbnailTypePref { + setThumbnailTypePref(pref); + return { type: SET_THUMBNAIL_TYPE_PREF, value: pref }; +} + +export function setAnimationPref( + pref: AnimationMotionPref, +): SetAnimationMotionPref { + setAnimationMotionPref(pref); + return { type: SET_ANIMATION_MOTION_PREF, value: pref }; +} + export function closeContextMenu(): AppThunk { return (dispatch, getState) => { if (getState().app.contextMenu.type !== ContextMenuType.Hidden) { @@ -453,6 +488,12 @@ export function initApp(): AppThunk { type: SET_DEFAULT_OPEN_TARGET, value: defaultOpenTarget, }); + const thumbnailPref = + await window.settings.getThumbnailTypePref(); + dispatch({ + type: SET_THUMBNAIL_TYPE_PREF, + value: thumbnailPref, + }); await dispatch(initSources()); }) .then(() => dispatch(initFeeds())) @@ -771,6 +812,16 @@ export function appReducer( ...state, defaultOpenTarget: action.value, }; + case SET_THUMBNAIL_TYPE_PREF: + return { + ...state, + thumbnailTypePref: action.value, + }; + case SET_ANIMATION_MOTION_PREF: + return { + ...state, + animationMotionPref: action.value, + }; case TOGGLE_LOGS: return { ...state, diff --git a/src/scripts/settings.ts b/src/scripts/settings.ts index 8f029e88..e1dfbde2 100644 --- a/src/scripts/settings.ts +++ b/src/scripts/settings.ts @@ -93,13 +93,13 @@ export function initThemeUpdateListener() { }); } -export function getThumbnailTypePref(): ThumbnailTypePref { +export async function getThumbnailTypePref(): Promise { return window.settings.getThumbnailTypePref(); } -export function setThumbnailTypePref(pref: ThumbnailTypePref) { - window.settings.setThumbnailTypePref(pref); +export function setThumbnailTypePref(pref: ThumbnailTypePref): Promise { + return window.settings.setThumbnailTypePref(pref); } -export function getAnimationMotionPref(): AnimationMotionPref { +export async function getAnimationMotionPref(): Promise { return window.settings.getAnimationMotionPref(); } export function setAnimationMotionPref(pref: AnimationMotionPref) { @@ -125,8 +125,8 @@ export function hasWindowFrame() { getNativeWindowFramePref() ); } -export function applyAnimationMotionPref() { - const pref = getAnimationMotionPref(); +export async function applyAnimationMotionPref(): Promise { + const pref = await getAnimationMotionPref(); let realisedPref = pref; if (pref === AnimationMotionPref.System) { const animationSettings = From 1cf975befb79da16d7d254a9ca99dbec55f4d51c Mon Sep 17 00:00:00 2001 From: CrystalSplitter Date: Sat, 24 Oct 2026 21:37:18 -0700 Subject: [PATCH 2/3] Pass animationMotionPref to cached-img --- src/components/cards/cached-img.tsx | 20 ++++++++++---------- src/components/cards/thumbnail.tsx | 15 ++++++++++++++- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/components/cards/cached-img.tsx b/src/components/cards/cached-img.tsx index 94a98fab..4b006c38 100644 --- a/src/components/cards/cached-img.tsx +++ b/src/components/cards/cached-img.tsx @@ -1,9 +1,9 @@ import * as React from "react"; import { AnimationMotionPref } from "../../schema-types"; -import { getAnimationMotionPref } from "../../scripts/settings"; type ImgProps = { src: string; + animationPref: AnimationMotionPref; className?: string; width?: number; height?: number; @@ -22,6 +22,7 @@ class CachedImg extends React.Component { | PlaceholderImageSource >(); private readonly _canvasRef = React.createRef(); + private _animationMotionPref: AnimationMotionPref; private _needsRescaling: boolean = true; private _imgSource: HTMLImageElement | HTMLVideoElement | VideoFrame[] | null = null; @@ -33,6 +34,7 @@ class CachedImg extends React.Component { constructor(props: ImgProps) { super(props); + this._animationMotionPref = props.animationPref; this._maxCanvasWidth = props.width ?? 512; this._maxCanvasHeight = props.height ?? 512; } @@ -140,15 +142,13 @@ class CachedImg extends React.Component { } private async renderImage(): Promise { - const realisedAmpPromise = getAnimationMotionPref().then( - (animationMotionPref) => - animationMotionPref !== AnimationMotionPref.System - ? animationMotionPref - : window.utils.systemPreferencesGetAnimationSettings() - .prefersReducedMotion - ? AnimationMotionPref.Off - : AnimationMotionPref.On, - ); + const realisedAmpPromise = + this._animationMotionPref !== AnimationMotionPref.System + ? this._animationMotionPref + : window.utils.systemPreferencesGetAnimationSettings() + .prefersReducedMotion + ? AnimationMotionPref.Off + : AnimationMotionPref.On; if (CachedImg._cache.has(this.props.src)) { const imgSource = CachedImg._cache.get(this.props.src); if (imgSource === placeholderImageSource) { diff --git a/src/components/cards/thumbnail.tsx b/src/components/cards/thumbnail.tsx index 670426f1..8583eb70 100644 --- a/src/components/cards/thumbnail.tsx +++ b/src/components/cards/thumbnail.tsx @@ -1,7 +1,10 @@ import * as React from "react"; import { RSSItem } from "../../scripts/models/item"; import CachedImg from "./cached-img"; -import { type ThumbnailTypePref } from "../../schema-types"; +import { + type AnimationMotionPref, + type ThumbnailTypePref, +} from "../../schema-types"; import { type RootState, useAppSelector } from "../../scripts/reducer"; type CardThumbnailProps = { @@ -13,6 +16,7 @@ type CardThumbnailProps = { const mediaElement = ( src: string, + animationPref: AnimationMotionPref, className: string, width: number | null, height: number | null, @@ -20,12 +24,18 @@ const mediaElement = ( return ( ); }; +/** Selector for animation motion preferences. */ +function useAnimationMotionPref(state: RootState) { + return state.app.animationMotionPref; +} + /** Selector for the thumbnail preference. */ function useThumbnailTypePref(state: RootState): ThumbnailTypePref { return state.app.thumbnailTypePref; @@ -33,6 +43,8 @@ function useThumbnailTypePref(state: RootState): ThumbnailTypePref { function CardThumbnail(props: CardThumbnailProps): React.JSX.Element { const preferredThumbnailType = useAppSelector(useThumbnailTypePref); + const animationMotionPref = useAppSelector(useAnimationMotionPref); + const preferredThumbnail = props.item.thumbnails?.filter( (t) => t.type === preferredThumbnailType, )?.[0]; @@ -40,6 +52,7 @@ function CardThumbnail(props: CardThumbnailProps): React.JSX.Element { return selectedThumbnail ? mediaElement( new URL(selectedThumbnail.url, props.item.link).toString(), + animationMotionPref, props.className, props.width, props.height, From 242903281db8c79de94590a9c824a127fb49e55a Mon Sep 17 00:00:00 2001 From: CrystalSplitter Date: Sun, 25 Oct 2026 15:03:53 -0700 Subject: [PATCH 3/3] Avoid fetching system animation motion pref window.utils and window.settings calls are extremely slow, and we should avoid calling them if we have to. Most images are not animated. In theory, this should probably also be part of the redux state. However, this is annoying to thread. So let's just move this lower and we won't have to worry about it. --- src/components/cards/cached-img.tsx | 103 +++++++++++++++------------- 1 file changed, 54 insertions(+), 49 deletions(-) diff --git a/src/components/cards/cached-img.tsx b/src/components/cards/cached-img.tsx index 4b006c38..6e7a1885 100644 --- a/src/components/cards/cached-img.tsx +++ b/src/components/cards/cached-img.tsx @@ -142,13 +142,6 @@ class CachedImg extends React.Component { } private async renderImage(): Promise { - const realisedAmpPromise = - this._animationMotionPref !== AnimationMotionPref.System - ? this._animationMotionPref - : window.utils.systemPreferencesGetAnimationSettings() - .prefersReducedMotion - ? AnimationMotionPref.Off - : AnimationMotionPref.On; if (CachedImg._cache.has(this.props.src)) { const imgSource = CachedImg._cache.get(this.props.src); if (imgSource === placeholderImageSource) { @@ -213,51 +206,63 @@ class CachedImg extends React.Component { this.forceUpdate(); return; } - } else if (this._imgSource instanceof HTMLVideoElement) { - const video = this._imgSource; - if (this._requestVideoFrameCallback !== null) { - video.cancelVideoFrameCallback( - this._requestVideoFrameCallback, - ); - this._requestVideoFrameCallback = null; - } - const requestFrame = async () => { - this.draw(video); - const realisedAnimationMotionPref = - await realisedAmpPromise; - if (realisedAnimationMotionPref === AnimationMotionPref.On) - this._requestVideoFrameCallback = - video.requestVideoFrameCallback(requestFrame); - }; - if (video.readyState < video.HAVE_CURRENT_DATA) - video.addEventListener("loadeddata", requestFrame); - else await requestFrame(); } else { - const frames = this._imgSource; - let frameIndex = 0; - if (this._animationTimeout) { - clearTimeout(this._animationTimeout); - this._animationTimeout = null; - } - const drawFrame = async () => { - if (frames.length === 0) return; - const frame = frames[frameIndex]; - const duration = frame.duration / 1000; - this.draw(frame); - const realisedAnimationMotionPref = - await realisedAmpPromise; - if ( - frames.length > 1 && - realisedAnimationMotionPref === AnimationMotionPref.On - ) { - frameIndex = (frameIndex + 1) % frames.length; - this._animationTimeout = setTimeout( - () => drawFrame(), - duration, + const realisedAmp = + this._animationMotionPref !== AnimationMotionPref.System + ? this._animationMotionPref + : window.utils.systemPreferencesGetAnimationSettings() + .prefersReducedMotion + ? AnimationMotionPref.Off + : AnimationMotionPref.On; + + if (this._imgSource instanceof HTMLVideoElement) { + const video = this._imgSource; + if (this._requestVideoFrameCallback !== null) { + video.cancelVideoFrameCallback( + this._requestVideoFrameCallback, ); + this._requestVideoFrameCallback = null; } - }; - await drawFrame(); + const requestFrame = async () => { + this.draw(video); + const realisedAnimationMotionPref = realisedAmp; + if ( + realisedAnimationMotionPref === + AnimationMotionPref.On + ) + this._requestVideoFrameCallback = + video.requestVideoFrameCallback(requestFrame); + }; + if (video.readyState < video.HAVE_CURRENT_DATA) + video.addEventListener("loadeddata", requestFrame); + else await requestFrame(); + } else { + const frames = this._imgSource; + let frameIndex = 0; + if (this._animationTimeout) { + clearTimeout(this._animationTimeout); + this._animationTimeout = null; + } + const drawFrame = async () => { + if (frames.length === 0) return; + const frame = frames[frameIndex]; + const duration = frame.duration / 1000; + this.draw(frame); + const realisedAnimationMotionPref = realisedAmp; + if ( + frames.length > 1 && + realisedAnimationMotionPref === + AnimationMotionPref.On + ) { + frameIndex = (frameIndex + 1) % frames.length; + this._animationTimeout = setTimeout( + () => drawFrame(), + duration, + ); + } + }; + await drawFrame(); + } } } }