The fairy of the product team suggested making a dynamic arrow to show the flow of data, but she didn’t know how to do it. So, I gave her two sets of effects. La la la la la
The implementation of ordinary arrows
For normal arrows, we can create a right-angled arrow by rotating the top and right borders of a square div by 45 degrees.
<style>
.wrap{
width: 10px;
height: 10px;
border-top: 2px solid red;
border-right: 2px solid red;
transform: rotate(45deg);
}
</style>
<div class="wrap"></div>
Copy the code
Our product wants multiple arrows with an obtuse Angle (greater than 90 degrees). so
Obtuse arrow
After a lot of thought and trial and error, I decided to simply use two lines to absolutely position an obtuse Angle.
<style>
.top{
width: 4px;
height: 10px;
transform: rotate(23deg);
position: relative;
top: -1px;
background-color: #FFBD1D;
}
.bottom{
width: 4px;
height: 10px;
transform: rotate(-23deg);
position: relative;
bottom: -1px;
background-color: rgb(255, 189, 29);
}
.arrow-wrap{
font-size: 0;
}
</style>
<div class="wrap">
<div class="arrow-wrap move">
<div class="arrow">
<div class="bottom"></div>
<div class="top"></div>
</div>
</div>
</div>
Copy the code
After 5 copies, we have the complete arrow, and with the complete arrow we can start to write the animation.
Animation is a
Five arrows moving together.
Move {animation: my-animation 2s; } @keyframes my-animation{ 0%{transform: translate(0px)} 25%{transform: translate(13px)} 50%{transform: translate(0px)} 75%{transform: translate(13px)} 100%{transform: translate(0px)} }Copy the code
The whole arrow can be rotated
.arrow-wrap{ display: inline-block; font-size: 0; transform: rotate(45deg) } .move{ animation: my-animation 2s; } @keyframes my-animation{ 0%{transform: translate(0px) rotate(45deg); } 25%{transform: translate(13px, 13px) rotate(45deg); } 50%{transform: translate(0px) rotate(45deg); } 75%{transform: translate(13px, 13px) rotate(45deg); } 100%{transform: translate(0px) rotate(45deg); }}Copy the code
Animation 2
The five arrows appear and disappear separately to create the illusion of movement. The page starts with five arrows :0; Any time after a fixed time, such as 90ms, the arrow is displayed successively, which can produce the effect of the arrow moving. We can use the animation’s delay to make each arrow appear in turn. We can also use JS to control the time interval to add style implementations to the 5 small arrows.
Pure CSS implementation
Without being too tedious, we define the animation property values for each small arrow by adding animation properties to the five. Arrow classes below the. Arrow-wrap class. The downside is that CSS animations only meet our sequential execution needs first.
- .arrow-wrap .arrow:nth-child(3){}; The third child of the. Arrow-wrap class is also the style of the arrow class element;
- animation-fill-mode: forwards; Controls the state of the last frame after the animation is completed, i.e., opacity: 1;
- The animation-delay property sets the execution delay for different arrows.
- Deferred execution is one-time and only the first time of the animation is valid. Then the arrows implemented through CSS can only be one-time and can only be animated once.
<style> .arrow{ display: inline-block; margin-left: 7px; opacity: 0; } .top{ width: 4px; height: 10px; transform: rotate(23deg); position: relative; top: -1px; background-color: rgb(255, 189, 29); } .bottom{ width: 4px; height: 10px; transform: rotate(-23deg); position: relative; bottom: -1px; background-color: rgb(255, 189, 29); } .arrow-wrap{ display: inline-block; min-width: 40px; font-size: 0; } .arrow-wrap .arrow:first-child{ animation-name: my-animation; Animation - duration: 0.1 s; animation-fill-mode: forwards; } .arrow-wrap .arrow:nth-child(2){ animation-name: my-animation; Animation - delay: 0.08 s; Animation - duration: 0.1 s; animation-fill-mode: forwards; } .arrow-wrap .arrow:nth-child(3){ animation-name: my-animation; Animation - delay: 0.18 s; Animation - duration: 0.1 s; animation-fill-mode: forwards; } .arrow-wrap .arrow:nth-child(4){ animation-name: my-animation; Animation - delay: 0.28 s; Animation - duration: 0.1 s; animation-fill-mode: forwards; } .arrow-wrap .arrow:last-child{ animation-name: my-animation; Animation - delay: 0.38 s; Animation - duration: 0.1 s; animation-fill-mode: forwards; } @keyframes my-animation{ 100%{opacity: 1; } } </style> <div class="wrap"> <div class="arrow-wrap"> <div class="arrow"> <div class="bottom"></div> <div class="top"></div> </div> <div class="arrow"> <div class="bottom"></div> <div class="top"></div> </div> <div class="arrow"> <div class="bottom"></div> <div class="top"></div> </div> <div class="arrow"> <div class="bottom"></div> <div class="top"></div> </div> <div class="arrow"> <div class="bottom"></div> <div class="top"></div> </div> </div> </div>Copy the code
setInterval
SetTimeout and setInterval are two functions that come to mind. SetTimeout indicates how long to delay execution. SetInterval indicates that the command is executed periodically at fixed intervals. Of course, we can actually use the setTimeout function to call itself to achieve the effect of periodic execution at fixed intervals.
The return value of setTimeout, timeoutID, is a positive integer, indicating the timer number. This value can be passed to clearTimeout() to cancel the timer.
The return value of setInterval, intervalID, is a non-zero value that identifies the timer created by setInterval(). This value can be used as an argument to clearInterval() to clear the timer.
The code to animate using setInterval is a bit cleaner:
<style> .ease-in{ animation-name: my-aimation; Animation - duration: 0.09 s; animation-fill-mode: forwards; } @keyframes my-aimation{ 100%{opacity: 1; } } </style> <script> const markers = document.getElementsByClassName('arrow'); let index = 0; // Animate the first arrow, and animate each arrow every 0.09s. markers[index].setAttribute("class", "arrow ease-in"); let shrinkTimer = setInterval(()=>{ index++; if(index == markers.length){ clearInterval(shrinkTimer); return; } markers[index].setAttribute("class", "arrow ease-in"); }, 90); </script>Copy the code
We can do this by modifying the logic in the setInterval function:
.ease-in{ animation-name: my-aimation; Animation - duration: 0.2 s; animation-fill-mode: forwards; } @keyframes my-aimation{ 100%{opacity: 1; } } <script> const markers = document.getElementsByClassName('arrow'); let index = 0; markers[index].setAttribute("class", "arrow ease-in"); let shrinkTimer = setInterval(()=>{ index++; if(index == markers.length){ index = 0; [...markers].forEach(item=>{ item.setAttribute("class", "arrow"); }); } markers[index].setAttribute("class", "arrow ease-in"); }, 200); </script>Copy the code
requestAnimationFrame
Most computer monitors refresh at 60Hz, which equates to about 60 redraws per second. Most browsers limit redrawing to the frequency at which the display can be redrawn, because beyond that the user experience will not improve. Therefore, the optimal loop interval for the smoothest animation is 1000ms/60, which is approximately 16.7ms. RequestAnimationFrame is a new HTML5 timer. It is up to the system to decide when to execute the callback function. In particular, the callback function in requestAnimationFrame is actively called before each drawing.
-
Tell the browser window. RequestAnimationFrame (fn) before you hope the next time the browser redraws the logic in the execution of fn function. Like window.setTimeout(fn, duration), it can only be executed once. If you want to achieve similar to setInterval periodically perform function fn, need to call again in the fn window. RequestAnimationFrame ()
-
Fn in window. RequestAnimationFrame (fn) is the function itself by a default parameters timestamp, the timestamp and the performance parameters, now () returns the same value, This represents the time when requestAnimationFrame() starts executing the callback function. We can understand it as a representation of time similar to date.now (). Timestamp represents time in the form of a floating point number with accuracy up to microseconds.
-
The return value of requestAnimationFrame is a long integer, the request ID, which is the unique identifier in the callback list. It’s a non-zero value, that’s all. You can send this value to the window. The cancelAnimationFrame () to cancel the callback function.
-
RequestAnimationFrame will not be executed on page B while looping the animation using requestAnimationFrame on page A because there is no content on page A to be redrawn to the screen.
The animation code using requestAnimationFrame looks like this:
<script>
let index = 0;
const markers = document.getElementsByClassName('arrow');
let count = 0;
let myReq;
const times = 5;
function loop(){
myReq = window.requestAnimationFrame(function(){
count++;
if(count%times === 0){
markers[index].setAttribute("class", "arrow ease-in");
index++;
}
loop();
});
if(count > times * markers.length){
window.cancelAnimationFrame(myReq);
}
}
loop();
</script>
Copy the code
thinking
Since born yu why born bright? Have setInterval and setTimeout timer to help us complete CSS can’t complete animation, why there are window. RequestAnimationFrame (fn) appear? Let’s analyze the problems of setInterval and setTimeout.
SetInterval problem analysis:
-
Duration indicates that the logic is executed according to the duration interval. Duration is not 16.7ms, for example 10ms. After 10ms some logic has been executed, but it will not render to the page until 16.7ms. The rendering process is as follows:
- 10ms perform the function of moving 1px; Render at 16.7ms
- 20ms performs the function of moving 2px; Don’t apply colours to a drawing
- 30ms perform the function of moving 3px; 33.4ms when rendering…
As you can see, the 20ms movement is not rendered, causing frame loss. There is a kind of stuttering and stuttering on the page. The reason for this problem is that the rendering frequency of the function is not consistent with the actual rendering frequency of the page. This is obviously not the case if we use requestAnimationFrame directly to control the animation.
-
Since it’s frequency inconsistency, can’t we just make them consistent? Yes, that’s ok. We try to keep our setInterval execution frequency consistent with the page rendering frequency (or the interval is a multiple of the screen rendering interval). But there are two things to note. On the one hand, the screen refresh frequency is affected by the screen resolution and screen size, and the screen drawing frequency may be different for different devices. We cannot directly fix the execution frequency of setInterval. SetInterval, on the other hand, is inherently indeterminate. Why do you say that? There are three reasons:
- Event loop mechanism
- The setInterval repeat timer is faulty.
- The setInterval is still executed when the TAB page is switched, and the page is not rendered.
Event loop
We all know that JavaScript is a single-threaded, non-blocking scripting language, which means that JavaScript code is executed with only one main thread to handle all tasks. Non-blocking means that the main thread suspends the asynchronous task when the code needs to process it. When the asynchronous task is finished, the main thread executes the corresponding callback according to certain rules.
In fact, when the task is finished, JavaScript adds the event to a queue, which we call an event queue. Events placed in the event queue do not execute their callbacks immediately, but wait for all tasks in the current execution stack to complete, and the main thread looks for any tasks in the event queue.
There are two types of asynchronous tasks: microtasks and macroTasks. Different types of tasks are assigned to different task queues.
When all tasks in the execution stack are executed (after the synchronization code is executed), it checks whether there are any events in the microtask queue. If there are, the callback corresponding to the events in the microtask queue will be executed successively until it is empty. Then fetch an event from the macro task queue and add the corresponding callback to the current execution stack. When all tasks in the execution stack are executed, check whether there is an event in the micro task queue. Repeat this process indefinitely, and an infinite loop is formed. This loop is called the event loop.
Events belonging to microtasks include but are not limited to:
- Promsie.then
- MutationObserver
- Object.observe
- process.nextTick
Events belonging to macro tasks include but are not limited to:
- setTimeout
- setInterval
- setImmediate
- MessageChannel
- requestAnimationFrame
- I/O
- UI interaction events
SetInterval and setTimeout are macro tasks. For complex JavaScript business code, the execution times of setInterval and setTimeout are uncertain. setTimeout(fn, duration); The browser simply adds fn to the macro task queue after duration, depending on how long the macro task is executed by the event loop.
Here is an interview question to explain in more detail:
console.log('script start') async function async1() { await async2() console.log('async1 end') } async function async2() { console.log('async2 end') } async1() setTimeout(function() { console.log('setTimeout') }, 0) new Promise(resolve => { console.log('Promise') resolve() }) .then(function() { console.log('promise1') }) .then(function() { console.log('promise2') }) console.log('script end')Copy the code
Script start async2 end Promise Script end AsynC1 end promise1 promise2 setTimeoutCopy the code
Async function async1(){async function async1(){async function async1(){ } new Promise(resolve=>{… ; Resolve ()}) code. Is the synchronization code. New Promise(). Then (res=>{… }); Is a microtask, which is put into the microtask queue, waiting to be executed. This is consistent with what I explained in another blog post juejin.cn/post/688367…
If you are careful, you will notice that requestAnimationFrame is also a macro task. Yes, requestAnimationFrame is also a macro task. Unlike setTimeout and setInterval, we do not need to consider whether the animation execution frequency is consistent with the screen rendering frequency. Animations implemented using requestAnimationFrame are much more silky.
The setInterval repeat timer is faulty
This is described in the book JavaScript Advanced Programming. We’ve already seen that setInterval is actually undetermined each time it executes. There are times when the function is not executed. The timer created with setInterval() ensures that the timer code is inserted regularly into the queue. The problem with this approach is that the timer code may not finish executing until the code is added to the queue again, resulting in the timer code running several times in a row without any pauses in between. Fortunately, JavaScript engines are smart enough to avoid this problem. When using setInterval(), the timer code is added to the queue only if there are no other code instances of the timer. This ensures that the minimum interval between the timer code joining the queue is the specified interval.
There are two problems with this repeat timer rule: (1) certain intervals will be skipped; (2) The interval between code execution of multiple timers may be smaller than expected.
JavaScript Advanced Programming also explains how to solve this problem by using setTimeout to call itself:
Function (){setTimeout(arguments.callee, interval); }, interval);Copy the code
This pattern chain calls setTimeout(), creating a new timer each time the function is executed. The second setTimeout() call uses arguments.callee to get a reference to the currently executing function and set another timer for it. The advantage of this is that no new timer code is inserted into the queue until the previous timer code has finished executing, ensuring that there are no missing intervals. Moreover, it ensures that at least a specified interval will be waited before the next timer code executes, avoiding successive runs. This mode is mainly used for repeat timers.
Is that perfect? SetTimeout () is a macro task that also has an indeterminate execution time. setTimeout(fn, duration); The browser simply adds fn to the macro task queue after duration, depending on how long the macro task is executed by the event loop. Using the setTimeout() call itself allows code to be executed repeatedly, rather than using setInterval directly.
The TAB page is switched
Animations using setTimeout or setInterval look fine when they overcome inconsistent rendering frequencies, but when we switch pages, wait a while, and then return to the animation page, something weird happens.
For example, we use setTimeout or setInterval to implement the wheel cast graph; After switching the page, setTimeout and setInterval functions are still executed, but the page does not continue to render and retains the position before switching. When we switch back to the page, the setTimeout and setInterval functions will be executed in different positions. This causes the animation to look incoherent.
There is a way around this, too, by listening for events when pages are hidden and activated. Clear the animation while the page is hidden, leaving the animation in its current state. Restart the animation when the page is active. The code can be found at juejin.cn/post/688361… The blog.
One more thing, I read a lot of blogs without introduction. RequestAnimationFrame does not execute when the page is switched, but if our code uses timestamp in the requestAnimationFrame callback, timestamp is incremented over time, Represents the time at which each callback is executed. Look at the following example:
const element = document.getElementById('myDiv'); let start; function step(timestamp) { if (start === undefined) start = timestamp; const elapsed = timestamp - start; Element.style. transform = 'translateX(' + elapsed + 'px)'; window.requestAnimationFrame(step); } window.requestAnimationFrame(step);Copy the code
Without switching pages, myDiv slides silkily around the page, but when we switch pages. Although the requestAnimationFrame function does not execute, when we cut back, the position of myDiv is not where we switched pages, Because the timestamp value in the requestAnimationFrame callback represents an objective value that increases over time.
conclusion
SetInterval:
- SetInterval is a macro task that may not execute in the desired chronological order due to the event loop mechanism;
- SetInterval also has the problem of repeating timers: the timer code is added to the queue only if there are no other code instances of the timer. This leads to two problems: (1) certain intervals are skipped; (2) The interval between code execution of multiple timers may be smaller than expected.
- The final point is that setInterval also executes during page transitions, causing incoherent animations. The solution is to listen for page activation and hide events.
SetTimeout:
- SetTimeout is also a macro task, which is affected by the event loop mechanism and may not execute at the expected time;
- By calling itself with setTimeout, the function can be executed repeatedly at fixed intervals, which is better than using setInterval directly.
- As with setInterval, the page is still executed after switching, and the animation is incoherent. The solution is to listen for page activation and hide events.
RequestAnimationFrame:
- RequestAnimationFrame solves the problem of setTimeout and setInterval implementing inconsistent animation and refresh rates, resulting in pages not being silky enough.
- RequestAnimationFrame itself does not execute after a page switch, which is both an advantage and a pitfall. Use according to the specific animation effect consideration.
- Finally, requestAnimationFrame is a new HTML5 timer that requires setTimeout to pollfy.
Let lastTime = 0 const prefixes = 'webkit moz ms o'.split(' ') // let requestAnimationFrame let cancelAnimationFrame const isServer = typeof window === 'undefined' if (isServer) { requestAnimationFrame = function() { return } cancelAnimationFrame = function() { return } } else { requestAnimationFrame = window.requestAnimationFrame CancelAnimationFrame = window. CancelAnimationFrame let the prefix / / prefix by iterating through the browser, RequestAnimationFrame cancelAnimationFrame for (let I = 0; i < prefixes.length; i++) { if (requestAnimationFrame && cancelAnimationFrame) { break } prefix = prefixes[i] requestAnimationFrame = requestAnimationFrame || window[prefix + 'RequestAnimationFrame'] cancelAnimationFrame = cancelAnimationFrame || window[prefix + 'CancelAnimationFrame'] || window[prefix + 'CancelRequestAnimationFrame'] } // If the current browser does not support requestAnimationFrame and cancelAnimationFrame, setTimeout if (! requestAnimationFrame || ! cancelAnimationFrame) { requestAnimationFrame = function(callback) { const currTime = new Date().getTime() // To make setTimteout as close to 60 frames per second as possible const timeToCall = math.max (0, 16 - (currTime - lastTime)) const id = window.setTimeout(() => { callback(currTime + timeToCall) }, timeToCall) lastTime = currTime + timeToCall return id } cancelAnimationFrame = function(id) { window.clearTimeout(id) } } } export { requestAnimationFrame, cancelAnimationFrame }Copy the code
Reference: www.cnblogs.com/onepixel/p/… www.cnblogs.com/xiaohuochai…
Thank you
If this article is helpful to you, please feel free to give a thumbs up. It will be my motivation to keep writing
You want to see a miracle, son? Young man, if you want a miracle, become one.