From dc8aa294a0ee901de5be50f38bdf9714c34e13bc Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 1 Jun 2025 22:46:01 +0200 Subject: [PATCH 1/2] Fix animation frames --- src/Elm/Kernel/Browser.js | 114 ++++++++++++++++++++++++++++++++------ 1 file changed, 97 insertions(+), 17 deletions(-) diff --git a/src/Elm/Kernel/Browser.js b/src/Elm/Kernel/Browser.js index 1d2de05..f6dff26 100644 --- a/src/Elm/Kernel/Browser.js +++ b/src/Elm/Kernel/Browser.js @@ -96,41 +96,121 @@ var _Browser_document = __Debugger_document || F4(function(impl, flagDecoder, de // ANIMATION -var _Browser_cancelAnimationFrame = - typeof cancelAnimationFrame !== 'undefined' - ? cancelAnimationFrame - : function(id) { clearTimeout(id); }; +var _Browser_requestAnimationFrame_queue = {}; +var _Browser_inAnimationFrame = false; +var _Browser_pendingAnimationFrame = false; +var _Browser_requestAnimationFrame_id = 0; -var _Browser_requestAnimationFrame = +function _Browser_cancelAnimationFrame(id) +{ + delete _Browser_requestAnimationFrame_queue[id]; +} + +function _Browser_requestAnimationFrame(callback) +{ + var id = _Browser_requestAnimationFrame_id; + _Browser_requestAnimationFrame_id++; + _Browser_requestAnimationFrame_queue[id] = callback; + if (!_Browser_pendingAnimationFrame) + { + _Browser_pendingAnimationFrame = true; + _Browser_requestAnimationFrame_raw(function() { + _Browser_pendingAnimationFrame = false; + _Browser_inAnimationFrame = true; + var maxId = _Browser_requestAnimationFrame_id; + for (var id2 in _Browser_requestAnimationFrame_queue) + { + if (id2 >= maxId) + { + break; + } + var callback = _Browser_requestAnimationFrame_queue[id2]; + delete _Browser_requestAnimationFrame_queue[id2]; + callback(); + } + _Browser_inAnimationFrame = false; + }); + } + return id; +} + +var _Browser_requestAnimationFrame_raw = typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame : function(callback) { return setTimeout(callback, 1000 / 60); }; +// Whether `draw` is currently running. `draw` can cause side effects: +// If the user renders a custom element, they can dispatch an event in +// its `connectedCallback`, which happens synchronously. That causes +// `update` to run while we’re in the middle of drawing, which then +// causes another call to the returned function below. We can’t start +// another draw while before the first one is finished. +// Another thing you can do in `connectedCallback`, is to initialize +// another Elm app. Even different app instances can conflict with each other, +// since they all use the same `_VirtualDom_renderCount` variable. +var _Browser_drawing = false; +var _Browser_drawSync_queue = []; function _Browser_makeAnimator(model, draw) { - draw(model); + // Whether we have already requested an animation frame for drawing. + var pendingFrame = false; - var state = __4_NO_REQUEST; + // Whether we have already requested to draw right after the current draw has finished. + var pendingSync = false; + + function drawHelp() + { + // If we’re already drawing, wait until that draw is done. + if (_Browser_drawing) + { + if (!pendingSync) + { + pendingSync = true; + _Browser_drawSync_queue.push(drawHelp); + } + return; + } + + pendingFrame = false; + pendingSync = false; + _Browser_drawing = true; + draw(model); + _Browser_drawing = false; + + while (_Browser_drawSync_queue.length > 0) + { + var callback = _Browser_drawSync_queue.shift(); + callback(); + } + } function updateIfNeeded() { - state = state === __4_EXTRA_REQUEST - ? __4_NO_REQUEST - : ( _Browser_requestAnimationFrame(updateIfNeeded), draw(model), __4_EXTRA_REQUEST ); + if (pendingFrame) + { + drawHelp(); + } } + drawHelp(); + return function(nextModel, isSync) { model = nextModel; - isSync - ? ( draw(model), - state === __4_PENDING_REQUEST && (state = __4_EXTRA_REQUEST) - ) - : ( state === __4_NO_REQUEST && _Browser_requestAnimationFrame(updateIfNeeded), - state = __4_PENDING_REQUEST - ); + // When using `Browser.Events.onAnimationFrame` we already are + // in an animation frame, so draw straight away. Otherwise we’ll + // be drawing one frame late all the time. + if (isSync || _Browser_inAnimationFrame) + { + drawHelp(); + } + else if (!pendingFrame) + { + pendingFrame = true; + _Browser_requestAnimationFrame(updateIfNeeded); + } }; } From 19ff487ec05306a16abf57e8fcefa6753d651a33 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 1 Jun 2025 22:57:00 +0200 Subject: [PATCH 2/2] Fix typo --- src/Elm/Kernel/Browser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Elm/Kernel/Browser.js b/src/Elm/Kernel/Browser.js index f6dff26..2b8c36a 100644 --- a/src/Elm/Kernel/Browser.js +++ b/src/Elm/Kernel/Browser.js @@ -144,7 +144,7 @@ var _Browser_requestAnimationFrame_raw = // its `connectedCallback`, which happens synchronously. That causes // `update` to run while we’re in the middle of drawing, which then // causes another call to the returned function below. We can’t start -// another draw while before the first one is finished. +// another draw before the first one is finished. // Another thing you can do in `connectedCallback`, is to initialize // another Elm app. Even different app instances can conflict with each other, // since they all use the same `_VirtualDom_renderCount` variable.