Home >Web Front-end >JS Tutorial >How to Implement Smooth Scrolling in Vanilla JavaScript
Core points
requestAnimationFrame
method to perform smooth animation updates, optimize performance and provide a smoother user experience. scroll-behavior
Properties to support native smooth scrolling in browsers that recognize this feature, and JavaScript fallback mechanism is provided if the browser does not support it. This article was peer-reviewed by Adrian Sandu, Chris Perry, Jérémy Heleine and Mallory van Achterberg. Thanks to all the peer reviewers of SitePoint to get the best content in SitePoint!
Smooth scrolling is a user interface mode that gradually enhances the default in-page navigation experience, animating the position within the scroll box (viewport or scrollable element), from the activation link position to the link URL The position of the target element indicated in the clip.
This is nothing new and has been a known pattern for years, for example, check out this SitePoint article dating back to 2003! By the way, this post is historically valuable because it shows how client-side JavaScript programming, especially DOM, has changed and evolved over the years, allowing for the development of easier native JavaScript solutions.
In the jQuery ecosystem, there are many implementations of this pattern, which can be implemented directly with jQuery or with plug-ins, but in this article, we are interested in pure JavaScript solutions. Specifically, we will explore and utilize the Jump.js library.
After introducing an overview of the library and its features and features, we will make some changes to the original code to suit our needs. In the process, we will review some core JavaScript language skills such as functions and closures. We will then create an HTML page to test the smooth scrolling behavior and then implement it as a custom script. Support for native smooth scrolling in CSS will then be added (if available), and finally we will make some observations on the browser navigation history.
This is the final demonstration we will create:
View Smooth Scrolling Pen for SitePoint (@SitePoint) on CodePen.
The complete source code can be found on GitHub.
Jump.js is written in native ES6 JavaScript and has no external dependencies. It is a small utility with only about 42 SLOC, but the minimization package provided is about 2.67 KB in size, as it has to be translated. A demo is provided on the GitHub project page.
As the name implies, it only provides jumps: the animation changes of the scroll bar position from its current value to the target position, specified by providing distances in the form of a DOM element, a CSS selector, or positive or negative numerical value. This means that in the implementation of smooth scroll mode, we have to perform link hijacking ourselves. See the section below for more information.
Please note that only vertical scrolling of the viewport is supported at present.
We can configure jumps with some options, such as duration (this parameter is required), easing function, and callbacks triggered at the end of the animation. We'll see their practical application later in the demo. See the documentation for complete details.
Jump.js runs on "modern" browsers without any problem, including Internet Explorer version 10 or later. Again, please refer to the documentation for a complete list of supported browsers. With the appropriate requestAnimationFrame polyfill, it can even run on older browsers.
Internally, the Jump.js source code uses the requestAnimationFrame method of the window object to arrange the position of the viewport vertical position updated in each frame of the scroll animation. This update is achieved by passing the next position value calculated using the easing function to the window.scrollTo method. See the source code for complete details.
We'll make some minor changes to the original code before delving into the demo to show how Jump.js is used, but that won't modify how it works internally.
The source code is written in ES6 and needs to be used with JavaScript build tools to translate and bundle modules. This may be a bit too much for some projects, so we will apply some refactoring to convert the code to ES5 for use anywhere.
First, let's remove ES6 syntax and features. The script defines an ES6 class:
<code>import easeInOutQuad from './easing' export default class Jump { jump(target, options = {}) { this.start = window.pageYOffset this.options = { duration: options.duration, offset: options.offset || 0, callback: options.callback, easing: options.easing || easeInOutQuad } this.distance = typeof target === 'string' ? this.options.offset + document.querySelector(target).getBoundingClientRect().top : target this.duration = typeof this.options.duration === 'function' ? this.options.duration(this.distance) : this.options.duration requestAnimationFrame(time => this._loop(time)) } _loop(time) { if(!this.timeStart) { this.timeStart = time } this.timeElapsed = time - this.timeStart this.next = this.options.easing(this.timeElapsed, this.start, this.distance, this.duration) window.scrollTo(0, this.next) this.timeElapsed ? requestAnimationFrame(time => this._loop(time)) : this._end() } _end() { window.scrollTo(0, this.start + this.distance) typeof this.options.callback === 'function' && this.options.callback() this.timeStart = false } } </code>
We can convert it to ES5 "classes" using constructors and a bunch of prototype methods, but note that we never need multiple instances of this class, so a singleton implemented with a normal object literal is fine :
<code>var jump = (function() { var o = { jump: function(target, options) { this.start = window.pageYOffset this.options = { duration: options.duration, offset: options.offset || 0, callback: options.callback, easing: options.easing || easeInOutQuad } this.distance = typeof target === 'string' ? this.options.offset + document.querySelector(target).getBoundingClientRect().top : target this.duration = typeof this.options.duration === 'function' ? this.options.duration(this.distance) : this.options.duration requestAnimationFrame(_loop) }, _loop: function(time) { if(!this.timeStart) { this.timeStart = time } this.timeElapsed = time - this.timeStart this.next = this.options.easing(this.timeElapsed, this.start, this.distance, this.duration) window.scrollTo(0, this.next) this.timeElapsed ? requestAnimationFrame(_loop) : this._end() }, _end: function() { window.scrollTo(0, this.start + this.distance) typeof this.options.callback === 'function' && this.options.callback() this.timeStart = false } }; var _loop = o._loop.bind(o); // Robert Penner's easeInOutQuad - http://robertpenner.com/easing/ function easeInOutQuad(t, b, c, d) { t /= d / 2 if(t t-- return -c / 2 * (t * (t - 2) - 1) + b } return o; })(); </code>
In addition to deleting the class, we need to make some other changes. The callback of requestAnimationFrame
is used to update the scrollbar position in each frame, and in the original code it is called via the ES6 arrow function, pre-bound to the jump singleton at initialization. We then bundle the default easing function in the same source file. Finally, we wrap the code using IIFE (call function expression immediately) to avoid namespace pollution.
Now we can apply another reconstruction step, note that with the help of nested functions and closures, we can just use functions instead of objects:
<code>function jump(target, options) { var start = window.pageYOffset; var opt = { duration: options.duration, offset: options.offset || 0, callback: options.callback, easing: options.easing || easeInOutQuad }; var distance = typeof target === 'string' ? opt.offset + document.querySelector(target).getBoundingClientRect().top : target ; var duration = typeof opt.duration === 'function' ? opt.duration(distance) : opt.duration ; var timeStart = null, timeElapsed ; requestAnimationFrame(loop); function loop(time) { if (timeStart === null) timeStart = time; timeElapsed = time - timeStart; window.scrollTo(0, opt.easing(timeElapsed, start, distance, duration)); if (timeElapsed requestAnimationFrame(loop) else end(); } function end() { window.scrollTo(0, start + distance); typeof opt.callback === 'function' && opt.callback(); timeStart = null; } // ... } </code>
Singleton is now a jump function that will be called to animate scrolling, loop and end callbacks become nested functions, and the properties of the object are now a local variable (closure). We no longer need IIFE, because now all the code is safely wrapped in a function.
As the final refactoring step, in order to avoid repeated timeStart reset checks every time the loop callback is called, the first time requestAnimationFrame() is called, we will pass it an anonymous function to it, which function is before calling the loop function Reset timerStart variable:
<code>import easeInOutQuad from './easing' export default class Jump { jump(target, options = {}) { this.start = window.pageYOffset this.options = { duration: options.duration, offset: options.offset || 0, callback: options.callback, easing: options.easing || easeInOutQuad } this.distance = typeof target === 'string' ? this.options.offset + document.querySelector(target).getBoundingClientRect().top : target this.duration = typeof this.options.duration === 'function' ? this.options.duration(this.distance) : this.options.duration requestAnimationFrame(time => this._loop(time)) } _loop(time) { if(!this.timeStart) { this.timeStart = time } this.timeElapsed = time - this.timeStart this.next = this.options.easing(this.timeElapsed, this.start, this.distance, this.duration) window.scrollTo(0, this.next) this.timeElapsed ? requestAnimationFrame(time => this._loop(time)) : this._end() } _end() { window.scrollTo(0, this.start + this.distance) typeof this.options.callback === 'function' && this.options.callback() this.timeStart = false } } </code>
Note again that during the reconstruction process, the core scroll animation code has not changed.
Now that we have customized the script to suit our needs, we are ready to assemble a test demo. In this section, we will write a page that enhances smooth scrolling using the scripts described in the next section.
This page contains a table of contents (TOC) that points to links within the page in the subsequent sections of the document, as well as other links to the TOC. We will also mix some external links to other pages. This is the basic structure of this page:
<code>var jump = (function() { var o = { jump: function(target, options) { this.start = window.pageYOffset this.options = { duration: options.duration, offset: options.offset || 0, callback: options.callback, easing: options.easing || easeInOutQuad } this.distance = typeof target === 'string' ? this.options.offset + document.querySelector(target).getBoundingClientRect().top : target this.duration = typeof this.options.duration === 'function' ? this.options.duration(this.distance) : this.options.duration requestAnimationFrame(_loop) }, _loop: function(time) { if(!this.timeStart) { this.timeStart = time } this.timeElapsed = time - this.timeStart this.next = this.options.easing(this.timeElapsed, this.start, this.distance, this.duration) window.scrollTo(0, this.next) this.timeElapsed ? requestAnimationFrame(_loop) : this._end() }, _end: function() { window.scrollTo(0, this.start + this.distance) typeof this.options.callback === 'function' && this.options.callback() this.timeStart = false } }; var _loop = o._loop.bind(o); // Robert Penner's easeInOutQuad - http://robertpenner.com/easing/ function easeInOutQuad(t, b, c, d) { t /= d / 2 if(t t-- return -c / 2 * (t * (t - 2) - 1) + b } return o; })(); </code>
At the head, we will include some CSS rules to set the basic simplest layout, and at the end of the body tag, we will include two JavaScript files: the former is our refactored version of Jump.js, and the latter is It's the script we'll discuss now.
This is a script that will enhance the test page scrolling experience using animated jumps from our customized version of Jump.js library. Of course, this code will also be written in ES5 JavaScript.
Let's brief overview of what it should do: it must hijack the clicks on the link within the page, disable the browser's default behavior (suddenly jump to the hash fragment of the href attribute of the click link that clicks on the link, and replace it with a call to our jump() function.
Therefore, first of all, you need to monitor the clicks on the links in the page. We can do this in two ways, using event delegates or attaching handlers to each related link.Event commission
<code>function jump(target, options) { var start = window.pageYOffset; var opt = { duration: options.duration, offset: options.offset || 0, callback: options.callback, easing: options.easing || easeInOutQuad }; var distance = typeof target === 'string' ? opt.offset + document.querySelector(target).getBoundingClientRect().top : target ; var duration = typeof opt.duration === 'function' ? opt.duration(distance) : opt.duration ; var timeStart = null, timeElapsed ; requestAnimationFrame(loop); function loop(time) { if (timeStart === null) timeStart = time; timeElapsed = time - timeStart; window.scrollTo(0, opt.easing(timeElapsed, start, distance, duration)); if (timeElapsed requestAnimationFrame(loop) else end(); } function end() { window.scrollTo(0, start + distance); typeof opt.callback === 'function' && opt.callback(); timeStart = null; } // ... } </code>Of course, now in the registered event listener (onClick), we have to check the target of the incoming click event object to check if it is related to the link element in the page. This can be done in a number of ways, so we will abstract it as a helper function isInPageLink(). We will look at the mechanism of this function later.
If the incoming click is on the in-page link, we will stop the event bubble and block the associated default action. Finally, we call the jump function, providing it with the hash selector of the target element and the parameters to configure the required animation.
This is the event handler:
<code>requestAnimationFrame(function(time) { timeStart = time; loop(time); }); function loop(time) { timeElapsed = time - timeStart; window.scrollTo(0, opt.easing(timeElapsed, start, distance, duration)); if (timeElapsed requestAnimationFrame(loop) else end(); } </code>Single handler
<code>import easeInOutQuad from './easing' export default class Jump { jump(target, options = {}) { this.start = window.pageYOffset this.options = { duration: options.duration, offset: options.offset || 0, callback: options.callback, easing: options.easing || easeInOutQuad } this.distance = typeof target === 'string' ? this.options.offset + document.querySelector(target).getBoundingClientRect().top : target this.duration = typeof this.options.duration === 'function' ? this.options.duration(this.distance) : this.options.duration requestAnimationFrame(time => this._loop(time)) } _loop(time) { if(!this.timeStart) { this.timeStart = time } this.timeElapsed = time - this.timeStart this.next = this.options.easing(this.timeElapsed, this.start, this.distance, this.duration) window.scrollTo(0, this.next) this.timeElapsed ? requestAnimationFrame(time => this._loop(time)) : this._end() } _end() { window.scrollTo(0, this.start + this.distance) typeof this.options.callback === 'function' && this.options.callback() this.timeStart = false } } </code>
Event handler is almost the same as before, but of course we don't need to check the click target:
<code>var jump = (function() { var o = { jump: function(target, options) { this.start = window.pageYOffset this.options = { duration: options.duration, offset: options.offset || 0, callback: options.callback, easing: options.easing || easeInOutQuad } this.distance = typeof target === 'string' ? this.options.offset + document.querySelector(target).getBoundingClientRect().top : target this.duration = typeof this.options.duration === 'function' ? this.options.duration(this.distance) : this.options.duration requestAnimationFrame(_loop) }, _loop: function(time) { if(!this.timeStart) { this.timeStart = time } this.timeElapsed = time - this.timeStart this.next = this.options.easing(this.timeElapsed, this.start, this.distance, this.duration) window.scrollTo(0, this.next) this.timeElapsed ? requestAnimationFrame(_loop) : this._end() }, _end: function() { window.scrollTo(0, this.start + this.distance) typeof this.options.callback === 'function' && this.options.callback() this.timeStart = false } }; var _loop = o._loop.bind(o); // Robert Penner's easeInOutQuad - http://robertpenner.com/easing/ function easeInOutQuad(t, b, c, d) { t /= d / 2 if(t t-- return -c / 2 * (t * (t - 2) - 1) + b } return o; })(); </code>
Which method is best depends on the context of use. For example, if new link elements may be added dynamically after the initial page loads, we must use event delegates.
Now we turn to the implementation of isInPageLink(), which we used this helper function in our previous event handler to abstract tests of in-page links. As we can see, this function takes a DOM node as a parameter and returns a Boolean value to indicate whether the node represents an in-page link element. It is not enough to just check that the passed node is an A tag and that hash fragment is set, because the link may be pointing to another page, in which case the default browser action must not be disabled. Therefore, we check if the value "minus" hash fragment stored in the property href is equal to the page URL:
<code>function jump(target, options) { var start = window.pageYOffset; var opt = { duration: options.duration, offset: options.offset || 0, callback: options.callback, easing: options.easing || easeInOutQuad }; var distance = typeof target === 'string' ? opt.offset + document.querySelector(target).getBoundingClientRect().top : target ; var duration = typeof opt.duration === 'function' ? opt.duration(distance) : opt.duration ; var timeStart = null, timeElapsed ; requestAnimationFrame(loop); function loop(time) { if (timeStart === null) timeStart = time; timeElapsed = time - timeStart; window.scrollTo(0, opt.easing(timeElapsed, start, distance, duration)); if (timeElapsed requestAnimationFrame(loop) else end(); } function end() { window.scrollTo(0, start + distance); typeof opt.callback === 'function' && opt.callback(); timeStart = null; } // ... } </code>
striphash() is another helper function, which we also use to set the value of the variable pageUrl when the script is initialized:
<code>requestAnimationFrame(function(time) { timeStart = time; loop(time); }); function loop(time) { timeElapsed = time - timeStart; window.scrollTo(0, opt.easing(timeElapsed, start, distance, duration)); if (timeElapsed requestAnimationFrame(loop) else end(); } </code>
This string-based solution and the pruning of hash fragments works fine even on URLs with query strings, because the hash parts are behind them in the general structure of the URL.
As I said before, this is just one possible way to implement this test. For example, the article cited at the beginning of this tutorial uses a different solution to perform component-level comparisons of link hrefs to location objects.
It should be noted that we use this function in both event subscription methods, but in the second method we use it as a filter for elements that we already know are tags, so The first check of the tagName property is redundant. This is left to the reader as an exercise.
For now, our code is susceptible to known errors (actually a pair of unrelated errors that affect Blink/WebKit/KHTML and one that affects IE) that affects keyboard users. When browsing the TOC link through the tab key, activating a link will smoothly scroll down to the selected section, but the focus will remain on the link. This means that when the next tab key is pressed, the user will be sent back to the TOC instead of the first link in the section of their choice.
To solve this problem, we will add another function to the main script:
<code>> <h1>></h1>Title> <nav> id="toc"></nav> <ul>></ul> <li>></li> <a> href="https://www.php.cn/link/db8229562f80fbcc7d780f571e5974ec"></a>Section 1>> <li>></li> <a> href="https://www.php.cn/link/ba2cf4148007ed8a8b041f8abd9bbf96"></a>Section 2>> ... > > id="sect-1"> <h2>></h2>Section 1> <p>></p>Pellentesque habitant morbi tristique senectus et netus et <a> href="https://www.php.cn/link/e1b97c787a5677efa5eba575c41e8688"></a>a link to another page> ac turpis egestas. <a> href="https://www.php.cn/link/e1b97c787a5677efa5eba575c41e8688index.html#foo"></a>A link to another page, with an anchor> quam, feugiat vitae, ...> <a> href="https://www.php.cn/link/7421d74f57142680e679057ddc98edf5"></a>Back to TOC> > id="sect-2"> <h2>></h2>Section 2> ... > ... src="jump.js">> src="script.js">> > </code>
It will run in the callback we will pass to the jump function and pass the hash of the element we want to scroll to past:
<code>import easeInOutQuad from './easing' export default class Jump { jump(target, options = {}) { this.start = window.pageYOffset this.options = { duration: options.duration, offset: options.offset || 0, callback: options.callback, easing: options.easing || easeInOutQuad } this.distance = typeof target === 'string' ? this.options.offset + document.querySelector(target).getBoundingClientRect().top : target this.duration = typeof this.options.duration === 'function' ? this.options.duration(this.distance) : this.options.duration requestAnimationFrame(time => this._loop(time)) } _loop(time) { if(!this.timeStart) { this.timeStart = time } this.timeElapsed = time - this.timeStart this.next = this.options.easing(this.timeElapsed, this.start, this.distance, this.duration) window.scrollTo(0, this.next) this.timeElapsed ? requestAnimationFrame(time => this._loop(time)) : this._end() } _end() { window.scrollTo(0, this.start + this.distance) typeof this.options.callback === 'function' && this.options.callback() this.timeStart = false } } </code>
The function of this function is to get the DOM element corresponding to the hash value and test whether it is already an element that can receive focus (such as an anchor or button element). If the element cannot receive focus by default (such as our container), it sets its tabIndex property to -1 (allows to receive focus programmatically, but not via the keyboard). The focus will then be set to that element, which means that the user's next tab key moves the focus to the next available link.
You can view the full source code of the main script here, with all the changes discussed previously.Support native smooth scrolling using CSS
. scroll-behavior
represents the default instantaneous scroll, and auto
represents the animation scroll. This specification does not provide any way to configure scroll animations, such as its duration and time functions (easing). smooth
Unfortunately, at the time of writing, support is very limited. In Chrome, this feature is under development and can be partially implemented by enabling it in the chrome://flags screen. The CSS property has not been implemented yet, so smooth scrolling on link clicks does not work.
Anyway, by making small changes to the main script, we can detect if this feature is available in the user agent and avoid running the rest of our code. To use smooth scrolling in the viewport, we apply the CSS attribute to the root element HTML (but in our test page we can even apply it to the body element):
<code>var jump = (function() { var o = { jump: function(target, options) { this.start = window.pageYOffset this.options = { duration: options.duration, offset: options.offset || 0, callback: options.callback, easing: options.easing || easeInOutQuad } this.distance = typeof target === 'string' ? this.options.offset + document.querySelector(target).getBoundingClientRect().top : target this.duration = typeof this.options.duration === 'function' ? this.options.duration(this.distance) : this.options.duration requestAnimationFrame(_loop) }, _loop: function(time) { if(!this.timeStart) { this.timeStart = time } this.timeElapsed = time - this.timeStart this.next = this.options.easing(this.timeElapsed, this.start, this.distance, this.duration) window.scrollTo(0, this.next) this.timeElapsed ? requestAnimationFrame(_loop) : this._end() }, _end: function() { window.scrollTo(0, this.start + this.distance) typeof this.options.callback === 'function' && this.options.callback() this.timeStart = false } }; var _loop = o._loop.bind(o); // Robert Penner's easeInOutQuad - http://robertpenner.com/easing/ function easeInOutQuad(t, b, c, d) { t /= d / 2 if(t t-- return -c / 2 * (t * (t - 2) - 1) + b } return o; })(); </code>Then, we add a simple functional detection test at the beginning of the script:
<code>function jump(target, options) { var start = window.pageYOffset; var opt = { duration: options.duration, offset: options.offset || 0, callback: options.callback, easing: options.easing || easeInOutQuad }; var distance = typeof target === 'string' ? opt.offset + document.querySelector(target).getBoundingClientRect().top : target ; var duration = typeof opt.duration === 'function' ? opt.duration(distance) : opt.duration ; var timeStart = null, timeElapsed ; requestAnimationFrame(loop); function loop(time) { if (timeStart === null) timeStart = time; timeElapsed = time - timeStart; window.scrollTo(0, opt.easing(timeElapsed, start, distance, duration)); if (timeElapsed requestAnimationFrame(loop) else end(); } function end() { window.scrollTo(0, start + distance); typeof opt.callback === 'function' && opt.callback(); timeStart = null; } // ... } </code>So if the browser supports native scrolling, the script will not do anything and exit, otherwise it will continue to execute as before and the browser will ignore unsupported CSS properties.
Conclusion
In the code we wrote (we can now think of it as a fallback scheme when CSS support is not available), we did not consider the behavior of scripts relative to browser history. Depending on the context and use case, this may or may not be something of interest, but if we think scripts should enhance the default scrolling experience, then we should expect consistent behavior, just like CSS.
Use native JavaScript to achieve smooth scrolling without using any libraries is very simple. You can use the window.scrollTo
method and set the behavior
option to smooth
. This method scrolls documents in the window by a given number of times. Here is a simple example:
<code>import easeInOutQuad from './easing' export default class Jump { jump(target, options = {}) { this.start = window.pageYOffset this.options = { duration: options.duration, offset: options.offset || 0, callback: options.callback, easing: options.easing || easeInOutQuad } this.distance = typeof target === 'string' ? this.options.offset + document.querySelector(target).getBoundingClientRect().top : target this.duration = typeof this.options.duration === 'function' ? this.options.duration(this.distance) : this.options.duration requestAnimationFrame(time => this._loop(time)) } _loop(time) { if(!this.timeStart) { this.timeStart = time } this.timeElapsed = time - this.timeStart this.next = this.options.easing(this.timeElapsed, this.start, this.distance, this.duration) window.scrollTo(0, this.next) this.timeElapsed ? requestAnimationFrame(time => this._loop(time)) : this._end() } _end() { window.scrollTo(0, this.start + this.distance) typeof this.options.callback === 'function' && this.options.callback() this.timeStart = false } } </code>
In this example, when you click on an element with class your-element
, the page will scroll smoothly to the top.
Smooth scrolling function using the scrollTo
method and setting the behavior
option to smooth
is not supported in Safari. To make it work you can use polyfill, such as smoothscroll-polyfill
. This will enable smooth scrolling in browsers that do not support it natively.
To scroll smoothly to a specific element, you can use the Element.scrollIntoView
method and set the behavior
option to smooth
. Here is an example:
<code>var jump = (function() { var o = { jump: function(target, options) { this.start = window.pageYOffset this.options = { duration: options.duration, offset: options.offset || 0, callback: options.callback, easing: options.easing || easeInOutQuad } this.distance = typeof target === 'string' ? this.options.offset + document.querySelector(target).getBoundingClientRect().top : target this.duration = typeof this.options.duration === 'function' ? this.options.duration(this.distance) : this.options.duration requestAnimationFrame(_loop) }, _loop: function(time) { if(!this.timeStart) { this.timeStart = time } this.timeElapsed = time - this.timeStart this.next = this.options.easing(this.timeElapsed, this.start, this.distance, this.duration) window.scrollTo(0, this.next) this.timeElapsed ? requestAnimationFrame(_loop) : this._end() }, _end: function() { window.scrollTo(0, this.start + this.distance) typeof this.options.callback === 'function' && this.options.callback() this.timeStart = false } }; var _loop = o._loop.bind(o); // Robert Penner's easeInOutQuad - http://robertpenner.com/easing/ function easeInOutQuad(t, b, c, d) { t /= d / 2 if(t t-- return -c / 2 * (t * (t - 2) - 1) + b } return o; })(); </code>
In this example, when you click on an element with class your-element
, the page will smoothly scroll to an element with class target-element
.
The speed of smooth scrolling cannot be directly controlled because it is handled by the browser. However, you can use window.requestAnimationFrame
to create a custom smooth scroll function for better control over the scrolling animation, including its speed.
You can achieve horizontal smooth scrolling in a similar way to vertical smooth scrolling. The window.scrollTo
and Element.scrollIntoView
methods also accept the left
options to specify the horizontal position to scroll to. Here is an example:
<code>function jump(target, options) { var start = window.pageYOffset; var opt = { duration: options.duration, offset: options.offset || 0, callback: options.callback, easing: options.easing || easeInOutQuad }; var distance = typeof target === 'string' ? opt.offset + document.querySelector(target).getBoundingClientRect().top : target ; var duration = typeof opt.duration === 'function' ? opt.duration(distance) : opt.duration ; var timeStart = null, timeElapsed ; requestAnimationFrame(loop); function loop(time) { if (timeStart === null) timeStart = time; timeElapsed = time - timeStart; window.scrollTo(0, opt.easing(timeElapsed, start, distance, duration)); if (timeElapsed requestAnimationFrame(loop) else end(); } function end() { window.scrollTo(0, start + distance); typeof opt.callback === 'function' && opt.callback(); timeStart = null; } // ... } </code>
This will make the document smoothly scroll 100 pixels to the right.
The smooth scrolling animation cannot be stopped directly because it is handled by the browser. However, if you are using a custom smooth scroll function, you can use window.cancelAnimationFrame
to cancel the animation frame to stop the animation.
To achieve smooth scrolling with fixed headers, you need to adjust the scroll position to take into account the height of the header. You can do this by subtracting the height of the header from the target scroll position.
To achieve smooth scrolling for anchor links, you can add event listeners to the link's click event and use the Element.scrollIntoView
method to scroll smoothly to the target element. Here is an example:
<code>requestAnimationFrame(function(time) { timeStart = time; loop(time); }); function loop(time) { timeElapsed = time - timeStart; window.scrollTo(0, opt.easing(timeElapsed, start, distance, duration)); if (timeElapsed requestAnimationFrame(loop) else end(); } </code>
This will smoothly scroll all anchor links on the page to its target element.
Using keyboard navigation to achieve smooth scrolling is more complicated because it requires intercepting keyboard events and manually scrolling documents. You can do this by adding an event listener to the keydown
event and scrolling the document smoothly using the window.scrollTo
method.
You can use online tools such as BrowserStack to test the compatibility of smooth scrolling. These tools allow you to test your website on different browsers and on different devices to ensure your implementation works properly across all environments.
The above is the detailed content of How to Implement Smooth Scrolling in Vanilla JavaScript. For more information, please follow other related articles on the PHP Chinese website!