preface
The first chapter describes several ways to achieve smooth rolling, and focus on the analysis of RAF, using RAF to achieve smooth rolling. I’m going to analyze the smooth-scroll library and look at other people’s implementations.
The core to realize
Firstly, the implementation of smooth-scroll is analyzed
var loopAnimateScroll = function (timestamp) {
if(! start) { start = timestamp; }// Elapsed is the total elapsed time
timeLapsed += timestamp - start;
// Speed represents the total time of the scrolling animation
percentage = speed === 0 ? 0 : (timeLapsed / speed);
//percentage indicates the current time progress. The range is [0,1].
// The value of percentage is greater than 1, indicating that the scroll is overdone
percentage = (percentage > 1)?1 : percentage;
position = startLocation + (distance * easingPattern(_settings, percentage));
window.scrollTo(0.Math.floor(position));
if(! stopAnimateScroll(position, endLocation)) { animationInterval =window.requestAnimationFrame(loopAnimateScroll); start = timestamp; }};/ /...
window.requestAnimationFrame(loopAnimateScroll);
Copy the code
Where loopAnimateScroll recursively calls itself using RAF. Callback has a timestamp parameter, which is provided by requestAnimationFrame. This parameter is a point in time in ms, but is a floating point number that can be accurate up to 5 microseconds depending on the hardware device. Meaning is the time when the callback is called. This is the same as performance.now().
You can see that the start parameter is initialized on the first execution, recording the start timestamp. TimeLapsed is the total elapsed time. Combined with the source context, speed represents the total time of the scrolling animation, and percentage represents the current time progress, ranging from 0 to 1.
position = startLocation + (distance * easingPattern(_settings, percentage));
Copy the code
Distance is the total distance and position is the current distance traveled. EasingPattern () accepts percentage and returns a coefficient.
Finally, scrollTo is used to scroll the frame to where it should be.
window.scrollTo(0.Math.floor(position));
Copy the code
There are two essential aspects of this implementation:
- It can accurately control the total time and speed of scrolling animation. Suppose we want to control the entire scrolling time to 2000ms, just set speed=2000. And just to simplify, let’s
easingPattern()
And what we return ispercentage
.
percentage = timeLapsed / speed;
// ...
position = startLocation + (distance * percentage);
// ...
window.scrollTo(0.Math.floor(position));
Copy the code
TimeLapsed increases as raf is called, and percentage increases linearly as timeLapsed. If percentage>=1, the time is in place and 2000ms has passed, so position is in place.
- The easingPattern() function can easily control the scrolling pattern, and returns percentage if it is linear. As long as easingPattern() satisfies the function relation that the return value of easingPattern() tends to 1 when percentage tends to 1 in the interval [0,1], it is ok.
Looking at some of the methods in the source code, the time variable is the percentage passed in, and the pattern variable is the return value of easingPattern()
EasingPattern (Settings, time) {// ...
if (settings.easing === 'easeInQuad')
pattern = time * time; // accelerating from zero velocity
if (settings.easing === 'easeOutQuad')
pattern = time * (2 - time); // decelerating to zero velocity
if (settings.easing === 'easeInOutQuad')
pattern = time < 0.5 ? 2 * time * time : - 1 + (4 - 2 * time) * time; // acceleration until halfway, then deceleration
// ...
return pattern
}
Copy the code
Here we compare the physical, mathematical function of the foundation. Pattern essentially represents distance S, time represents time T, and v(t) relationship can be obtained by differentiating S (t). If V (t) is constant, it means uniform motion, and a(t) relationship can be obtained by differentiating again. A (t)>0 means acceleration, a(t)=0 means uniform speed, and a(t)<0 means deceleration.
So if there is a pattern which is pattern = time, the roll is uniform, the top three are accelerating from 0, decelerating to 0, accelerating first and then decelerating.
The frequency of raf execution is irrelevant here. For example, RAF is a series of points falling on the continuous function y= easingPattern(x)
We can customize some functions to extend the scrolling mode.
Cancel the scroll
This library implements the unscrolling method and exposes it to call directly. RequestAnimationFrame returns an ID, which is passed in by cancelAnimationFrame. If the requestAnimationFrame callback has not been executed, it will be cancelled. So the RAF becomes controllable, and this is sort of like a clear timer, making the program controllable.
animationInterval = window.requestAnimationFrame(loopAnimateScroll);
Copy the code
smoothScroll.cancelScroll = function (noEvent) {
cancelAnimationFrame(animationInterval);// If you pass in the id, the cb in raf is cancelled
animationInterval = null;
if (noEvent) return;
emitEvent('scrollCancel', settings); // Distributes the scrollCancel event
};
Copy the code
To realize the hook
We always want our program to be a controlled state machine. When we use third-party libraries, we want them to expose hooks so that we can execute our scripts before or after the third-party libraries have done something. Like Vue, React provides a variety of lifecycle hooks, such as Git, which can execute arbitrary scripts before committing.
Smooth-scroll creates three custom events and distributes them before or after the corresponding operation.
- scrollStart is emitted when the scrolling animation starts.
- scrollStop is emitted when the scrolling animation stops.
- scrollCancel is emitted if the scrolling animation is canceled.
Functions that create events and distribute them
var emitEvent = function (type, options, anchor, toggle) {
if(! options.emitEvents ||typeof window.CustomEvent ! = ='function') return;
var event = new CustomEvent(type, {
bubbles: true.detail: {
anchor: anchor,
toggle: toggle
}
});
document.dispatchEvent(event);
};
Copy the code
After performing the Cancel operation in the source code, create and distribute the scrollCancel event
smoothScroll.cancelScroll = function (noEvent) {
cancelAnimationFrame(animationInterval);// If you pass in the id, the cb in raf is cancelled
animationInterval = null;
if (noEvent) return;
emitEvent('scrollCancel', settings); // Distributes the scrollCancel event
};
Copy the code
As a user we only need to write this to call our own callback function in the corresponding hook.
document.addEventListener('scrollCancel', callback, false);
Copy the code
Click processing and implementation
After the new instance, the library is bound to the A tag by default and reads the hash of the TAG to locate the element to scroll to.
Clicking on non-A tags is not supported in this usage. The library also exposes the Animate API, which allows scrolling directly to the appropriate location. Using this API in the Button click event triggers scrolling.
This library is great if the page has many anchors and corresponds to many DOM elements (reading a cataloged article or reading a document on the Web might be a good fit).
Start by listening for click on the entire document
document.addEventListener('click', clickHandler, false);
Copy the code
Next, find the DOM element you want to scroll to
function clickHandler(){
// ...
//selector is usually a[href*="#"]
Closest is the closest to finding the selector element in the parent element, because the A tag sometimes wraps around the img tag, span,
// It is not event.target
toggle = event.target.closest(selector);
// Toggle does not return with an A tag
if(! toggle || toggle.tagName.toLowerCase() ! = ='a' || event.target.closest(settings.ignore)) return;
// ...
var hash = escapeCharacters(toggle.hash);
var anchor;
if (hash === The '#') {
if(! settings.topOnEmptyHash)return;
anchor = document.documentElement;
} else {
anchor = document.querySelector(hash);
}
// Anchor is the DOM element that the hash points to
}
Copy the code
The implementation of this listening mode is consistent with low coupling. If you want to do that, you can do it in one line of code by importing this library.
var scroll = new SmoothScroll('a[href*="#"]');
Copy the code
To delete the slide, simply delete this part of the code.
Implement the History animation
Prevented the default event of the A tag in the click event, which would otherwise trigger the default behavior of the anchor. Also use the HISTORY API and update the URL to add the hash value to the URL. This allows you to click the browser back forward, listen for the popState event, return to the previous hash location, and implement the default animation for smooth scrolling.
var popstateHandler = function (event) {
var anchor = history.state.anchor;
if (typeof anchor === 'string' && anchor) {
anchor = document.querySelector(escapeCharacters(history.state.anchor));
if(! anchor)return;
}
smoothScroll.animateScroll(anchor, null, {updateURL: false});
Copy the code
You don’t use updateURL when you rollback
UpdateURL uses history.pushState to keep the history record
var updateURL = function (anchor, isNum, options) {
// Bail if the anchor is a number
if (isNum) return;
// Verify that pushState is supported and the updateURL option is enabled
if(! history.pushState || ! options.updateURL)return;
// Update URL
history.pushState(
{
smoothScroll: JSON.stringify(options),
anchor: anchor.id
},
document.title,
anchor === document.documentElement ? '#top' : The '#' + anchor.id
);
};
Copy the code
Implement destruction to avoid memory leaks
smoothScroll.destroy = function () {
// If plugin isn't already initialized, stop
if(! settings)return;
// Remove event listeners
document.removeEventListener('click', clickHandler, false);
window.removeEventListener('popstate', popstateHandler, false);
// Cancel any scrolls-in-progress
smoothScroll.cancelScroll();
// Reset variables
settings = null;
anchor = null;
toggle = null;
fixedHeader = null;
eventTimeout = null;
animationInterval = null;
};
Copy the code
conclusion
Reading the source code of the open source library can always harvest a lot, not only the design of the function, but also the practice of the design principle of the software.