The 2021.6 update
In this paper, records were sorted out earlier, and some contents were not thoroughly understood, even causing misunderstandings. This article is for reference, but some details need to be checked by the reader.
I will restudy and sort out when I have time later.
With the development of technology and equipment, the user’s terminal becomes more and more capable of animation, and more scenes begin to use animation in large quantities. In Web applications, there are many ways to realize animation effects, such as setTimeout timer in JavaScript, Transition and animation in CSS3, and canvas in HTML5. Additionally, HTML5 provides a dedicated API for requesting animations, requestAnimationFrame
The content of this article is not original, but in the collection of knowledge and handling learning and understanding, you are welcome to collect and handling this article!
1. What is
-
HTML5’s new API, similar to setTimeout timer
-
A window object method, window. RequestAnimationFrame
partial interface Window { long requestAnimationFrame(FrameRequestCallback callback); void cancelAnimationFrame(long handle); }; Copy the code
-
Browser-only (and therefore browser-only) API for animation that gives DOM animation, Canvas animation, SVG animation, WebGL animation, etc a unified refresh mechanism
2. What to do
- The browser redraw rate generally keeps pace with the monitor refresh rate. Most browsers follow W3C recommendations and render pages at a standard frame rate of 60FPS (frames/ per second).
-
Redraw a web page by frame. This method tells the browser that it wants to animate and asks the browser to call a callback to update the animation before the next redraw
-
It is up to the system to decide when to execute the callback function, and the browser will optimize the method invocation automatically at runtime
-
The basic idea of requestAnimationFrame synchronizes page redrawing with a fixed refresh rate (60Hz or 75Hz) of the display, which means that it can only be redrawn 60 or 75 times per second
For example, if the monitor screen refresh rate is 60Hz and the requestAnimationFrame API is used, then the callback function is executed every 1000ms / 60 ≈ 16.7ms; If the monitor screen refresh rate is 75Hz, the callback function is executed every 1000ms / 75 ≈ 13.3ms.
-
The interval between page redrawing or rewinding caused by calling the callback function through requestAnimationFrame is the same as the interval between display refresh. So requestAnimationFrame doesn’t need to pass the interval like setTimeout, but rather the browser gets the display refresh frequency from the system and uses it
For example, for an animation, the width increases from 0px plus one to 100px. In the case of no easing, the browser redraws once, increasing the width by 1.
-
3. Usage
List of animation Frame callback requests: Each Document has a list of animation frame callback requests, which can be thought of as a collection of
,>
tuples.
handle
Is an integer that uniquely identifies a tuple’s position in a list,cancelAnimationFrame()
You can stop the animation with thiscallback
Is a function that returns no value and takes a time value as the number of milliseconds passed in by the browser from January 1, 1970 to the current date.- The list is initially empty.
Page visibility API
- When a page is minimized or switched to a background TAB, the page is invisible and the browser triggers one
visibilitychange
Event and setdocument.hidden
Properties fortrue
- When the page switches to the display state, the page becomes visible and triggers a
visibilitychange
Event, Settingsdocument.hidden
Properties forfalse
-
Invoke the operation. Similar to setTimeout, but without setting the interval, a callback function is used as an argument that returns an integer greater than 0
handle = requestAnimationFrame(callback); Copy the code
- parameter
callback
, is a callback function that is called the next time the animation is redrawn. This callback takes a unique argument, which is a high-precision timestamp (performance.now()
), the current time when the callback is triggered (no manual passing) - The return value is one
long
A non-zero integer of type, isrequestAnimationFrame
A unique identifier in the callback function list. It indicates the timer number
- parameter
-
Cancel the operation
cancelAnimationFrame(handle); Copy the code
- The argument is to call
requestAnimationFrame
Is the return value of - There is no return value from the cancel operation
- The argument is to call
-
Browser execution
-
First check if the Document.hidden property is true (the page is visible), and then perform the next step if the page is visible
-
The browser clears the animation function from the previous round
-
RequestAnimationFrame appends the callback function to the end of the list of callback functions requested by the animation frame
When requestAnimationFrame(callback) is executed, the callback function is not called immediately; it is simply put into a queue. Each callback function has a Boolean cancelled that has an initial value of false and is invisible.
-
When the browser executes the list’s callback, cancelled each tuple’s callback and executed if false
When the page is visible and the list of callback functions requested by the animation frame is not empty, the browser periodically adds these callback functions to the queue in the browser UI thread
-
RequestAnimationFrame provides a pseudo-code that describes the steps of the “sample all animations” task
var list = {}; varBrowsingContexts = Browser top-level context and its subordinate browser context;for (var browsingContext in browsingContexts) { / *! Changing the time value from DOMTimeStamp to DOMHighResTimeStamp is the latest change in the W3C's latest draft edit for the script-based animation timing control specification, * and some vendors still implement it as DOMTimeStamp. * Older versions of the W3C specification use DOMTimeStamp, which allows you to use date.now for the current time. * As mentioned above, some browser vendors may still implement the DOMTimeStamp parameter, or they may not yet implement the window.performance. Now timing function. * So the user needs to polyfill */ var time = DOMHighResTimeStamp // High precision time measured from the beginning of the page navigation. DOMHighResTimeStamp is measured in milliseconds, accurate to one thousandth of a millisecond. This time value is not directly compared to date.now (), which measures the time in milliseconds from January 1, 1970 to the present. If you want to compare the time parameter to the current time, use window.performance. Now for the current time. varD = active of browsingContextdocument; // That is the Document node in the current browser context // If active document is visible if(d.hidden ! = =true) { // Copy the active Document animation frame to request the list of callback functions and empty the list varDoclist = d animate frame request callback function list doclist. AppendTo (list); clear(doclist); }// Iterate over the callback functions in the tuple of the list of callback functions requested by the animation frame for (var callback in list) { if(callback.cancelled ! = =true) { try { // Each browsingContext has a corresponding WindowProxy object, which points callback to the Window object associated with the Active Document. // Pass the time value time callback.call(window, time); } // Ignore exceptions catch (e) { } } } } Copy the code
-
When cancelAnimationFrame(Handle) is called, the browser sets cancelled to true for the callback to which the handle points (whether or not the callback is in the list of animation frame request callbacks). If the Handle does not point to any callback function, nothing happens.
-
-
Recursive call. To achieve a complete animation, the callback function should be called recursively within the callback function
let count = 0; let rafId = null; /** * callback function *@param Time requestAnimationFrame A time that is automatically passed in when the function is called */ function requestAnimation(time) { console.log(time); // Render recursively if the animation is not finished if (count < 50) { count++; // Render the next framerafId = requestAnimationFrame(requestAnimation); }}Render the first frame requestAnimationFrame(requestAnimation); Copy the code
-
If requestAnimationFrame calls the same callback function multiple times before the callback function is executed or the Document animation frame requests that the list of callback functions be cleared, there will be multiple tuples pointing to the callback function in the list (they have different handles, Callback is the callback function, which is executed multiple times by the “Collect All Animations” task. (Analogy of timer setTimeout)
function counter() { let count = 0; function animate(time) { if (count < 50) { count++; console.log(count); requestAnimationFrame(animate); } } requestAnimationFrame(animate); } btn.addEventListener("click", counter, false); Copy the code
-
Click the button multiple times, and you will see multiple sequence values printed (in the picture below, three sequences are printed after three consecutive triggers).
-
If it is applied to animation, animation will appear mutation
-
4. The compatibility
- International practice caniuse
- MDN(window.requestAnimationFrame)
- Modern browser versions include basic mobile support, so it’s hard to avoid using ancestral versions, so it’s still an elegant downgrade
Source: Polyfill for requestAnimationFrame/cancelAnimationFrame
Execute the following code when the browser first loads.
// Using date.now to get timestamp performance is more efficient than using new Date().getTime
if (!Date.now)
Date.now = function() {
return new Date().getTime();
};
(function() {
"use strict";
var vendors = ["webkit"."moz"];
for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) {
var vp = vendors[i];
window.requestAnimationFrame = window[vp + "RequestAnimationFrame"];
window.cancelAnimationFrame =
window[vp + "CancelAnimationFrame"] | |window[vp + "CancelRequestAnimationFrame"];
}
// for cases where none of the above methods are supported, and for IOS6 devices
// Use setTimeout to simulate implementation
if (
/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) ||
!window.requestAnimationFrame ||
!window.cancelAnimationFrame
) {
var lastTime = 0;
// This is similar to the function that implements throttling by timestamp
window.requestAnimationFrame = function(callback) {
var now = Date.now();
var nextTime = Math.max(lastTime + 16, now);
Nexttime-now = 0 for the first time
return setTimeout(function() {
callback((lastTime = nextTime));
}, nextTime - now);
};
window.cancelAnimationFrame = clearTimeout;
}
})();
Copy the code
Advantages of 5.
RequestAnimationFrame uses system time intervals to maintain optimal drawing efficiency. Not because the interval time is too short, resulting in excessive drawing, increase overhead; The animation will not lag because the interval time is too long.
RequestAnimationFrame is similar to timer setTimeout in terms of functionality and usage, so it has an advantage over animation implemented by setTimeout.
A. Improve performance and prevent frame loss
- Browser UI threads: Browsers share a single thread for executing JavaScript and updating the user interface (including redrawing and reflow), called the “browser UI thread.”
- The browser UI thread works based on a simple queue system, where tasks are stored until the process is idle. Once idle, the next task in the queue is extracted and run again. These tasks are either running JavaScript code or performing UI updates.
-
Animate with setTimeout
-
SetTimeout animates an image by constantly changing it at an interval. This method will appear stutter and jitter on some low-end machines. There are generally two reasons for this phenomenon:
-
The execution time of setTimeout is not fixed.
In JavaScript, the setTimeout task is placed in an asynchronous queue, and only after the main thread is finished will the queue be checked to see if the task needs to be executed. Therefore, the actual execution time of setTimeout is usually later than the specified time. This runtime mechanism determines that the interval parameter really only specifies how long the animation code should be added to the browser UI thread queue for execution. If other tasks have already been added to the queue, the animation code will wait for the previous task to complete
let startTime = performance.now(); setTimeout(() = > { let endTime = performance.now(); console.log(endTime - startTime); }, 50); /* A very time-consuming task */ for (let i = 0; i < 20000; i++) { console.log(0); } Copy the code
-
The refresh frequency is affected by the screen resolution and screen size, and the screen refresh rate may be different on different devices. SetTimeout can only set a fixed interval, which may be different from the screen refresh interval
-
-
In both cases, the execution pace of setTimeout is inconsistent with the refresh pace of the screen, resulting in frame loss.
-
The setTimeout execution only changes the image properties in memory, and this change will not be updated to the screen until the next browser redraws. If not synchronized with the screen refresh, it is possible to skip over some intermediate frames and update the image directly from the next frame.
Let’s say a timer is set to execute a frame at an interval of 10ms, and the browser refresh interval is 16.6ms (60FPS).
As can be seen from the figure, at 20ms, setTimeout calls the callback function to modify the attributes of the image in memory, but the next refresh of the browser at this time is 33.2ms, so the image modified at 20ms is not updated to the screen. When it comes to 30ms, setTimeout calls the callback function again and changes the properties of the image in memory. Then the browser is refreshed, and the updated state of 20ms is covered by the image of 30ms. The image of 30ms is displayed on the screen, so the frame of 20ms is lost. If you lose too many frames, the picture gets stuck.
-
-
-
The biggest advantage of using requestAnimationFrame for animation is that it ensures that the callback function is executed only once in every screen refresh interval, so that frames are not lost and the animation is not stuck
B. Save resources and power
-
For animations implemented with setTimeout, when the page is hidden or minimized, the timer setTimeout is still performing the animation task in the background, and there is no point in refreshing the animation at all (actually FireFox/Chrome has optimized the timer: If the interval between idle pages is less than 1000ms, the timer is stopped, similar to requestAnimationFrame behavior. If the interval is >=1000ms, the timer is still executed in the background.)
// Execute the following code on the Console page of the browser developer tools. // After starting to print count, switch to the browser TAB and switch back to find that the printed value has not stopped, and may even have finished executing let count = 0; let timer = setInterval(() = > { if (count < 20) { count++; console.log(count); } else { clearInterval(timer); timer = null; }},2000); Copy the code
-
With requestAnimationFrame, while the page is inactive, the screen refresh task for that page is paused by the system, as is requestAnimationFrame, which keeps the screen refresh execution synchronized. When the page is activated, the animation picks up where it left off, saving CPU overhead.
// Execute the following code on the Console page of the browser developer tools. // After starting to print count, switch to the browser TAB and then switch back to find that the printed value continues to print from the value before leaving let count = 0; function requestAnimation() { if (count < 500) { count++; console.log(count); requestAnimationFrame(requestAnimation); } } requestAnimationFrame(requestAnimation); Copy the code
C. Function throttling
- It does not make sense if the function is executed more than once in a refresh interval, because the monitor is refreshed every 16.7ms and multiple draws do not show up on the screen
- At high frequency events (
resize
.scroll
And so onrequestAnimationFrame
It prevents multiple function executions within a refresh interval, which ensures smoothness and saves on function execution overhead - In some cases it can be used directly
requestAnimationFrame
Instead of Throttle, you limit how often the callback is executed
6. Application
-
Simple progress bar animation
function loadingBar(ele) { // Use closure to save timer number let handle; return () = > { // Clears progress with each trigger ele.style.width = "0"; // Clear the last animation timer before starting animation // Otherwise, multiple timers will be started cancelAnimationFrame(handle); // The callback function let _progress = () = > { let eleWidth = parseInt(ele.style.width); if (eleWidth < 200) { ele.style.width = `${eleWidth + 5}px`; handle = requestAnimationFrame(_progress); } else{ cancelAnimationFrame(handle); }}; handle = requestAnimationFrame(_progress); }; }Copy the code
-
Add an ease-in-out effect to make an element block move according to third-order Bessel curve ease-in effect parameters. How to implement easing effects using Javascript
Slow animation: Specifies the speed at which an animation effect should be executed to make it look more realistic.
/ * * *@param {HTMLElement} Ele element node *@param {number} Change the amount of change *@param {number} Duration Animation duration */
function moveBox(ele, change, duration) {
// Use closures to save timer identifiers
let handle;
// return the animation function
return () = > {
// Start time
let startTime = performance.now();
// Prevent multiple timers from starting
cancelAnimationFrame(handle);
// The callback function
function _animation() {
// The start time of this frame
let current = performance.now();
let eleTop = ele.offsetLeft;
// The distance the element moves in this frame
let left = change * easeInOutCubic((current - startTime) / duration);
ele.style.left = `${~~left}px`;
// Determine if the animation is finished
if ((current - startTime) / duration < 1) {
handle = requestAnimationFrame(_animation);
} else{ cancelAnimationFrame(handle); }}// Start frame 1
handle = requestAnimationFrame(_animation);
};
}
/** * Ease-in-out *@param {number} k* /
function easeInOutCubic(k) {
return (k *= 2) < 1 ? 0.5 * k * k * k : 0.5 * ((k -= 2) * k * k + 2);
}
Copy the code
7. The related
The content of this article is not original, but in the collection of knowledge and handling learning and understanding, you are welcome to collect and handling this article!
- Developer.mozilla.org/zh-CN/docs/…
- Caniuse.com/#search=req…
- www.zhangxinxu.com/wordpress/2…
- Javascript.ruanyifeng.com/htmlapi/req…
- www.cnblogs.com/xiaohuochai…
- Juejin. Cn/post / 684490…
- www.cnblogs.com/chaogex/p/3…
- www.softwhy.com/article-720…
- easings.net/zh-cn#
- zhuanlan.zhihu.com/p/25676357
- www.cnblogs.com/onepixel/p/…