亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

Table of Contents
Jump.js
Quickly understand behind the screen
Some customizations
Test Page
Main script
Accessibility considerations
FAQs (FAQs) on Smooth Scrolling with Native JavaScript
How to achieve smooth scrolling using native JavaScript without using any libraries?
Why does my smooth scroll not work in Safari?
How to scroll smoothly to a specific element?
Can I control the speed of smooth scrolling?
How to achieve horizontal smooth scrolling?
How to stop smooth scrolling animation?
How to achieve smooth scrolling with fixed headers?
How to achieve smooth scrolling for anchor links?
How to use keyboard navigation to achieve smooth scrolling?
How to test the compatibility of my smooth scroll implementation?
Home Web Front-end JS Tutorial How to Implement Smooth Scrolling in Vanilla JavaScript

How to Implement Smooth Scrolling in Vanilla JavaScript

Feb 18, 2025 am 10:49 AM

How to Implement Smooth Scrolling in Vanilla JavaScript

Core points

  • Use the Jump.js library to implement native JavaScript smooth scrolling, simplifying scrolling animation without external dependencies.
  • Modify Jump.js original code to convert it from ES6 to ES5 to ensure wider compatibility with different browsers.
  • Use the requestAnimationFrame method to perform smooth animation updates, optimize performance and provide a smoother user experience.
  • Implement custom JavaScript to intercept the default in-page link behavior, and replace sudden jumps with smooth scrolling animations.
  • Integrated CSS 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.
  • Ensure accessibility by setting focus to target elements after scrolling, addressing potential issues with keyboard navigation, and enhancing usability for all users.

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

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.

Quickly understand behind the screen

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.

Some customizations

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.

Test Page

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.

Main script

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

In the first method, we add the click listener to an element document.body. This way, each click event of any element on the page will bubbling along its ancestor's branches into the DOM tree until it reaches document.body:

<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

Use the second method to monitor link clicks, attaching the slightly modified version of the event handler described above to the link element within each page, so there is no event bubble:

<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 query all elements and use the [].slice() trick to convert the returned DOM NodeList into a JavaScript array (a better alternative if the target browser supports it is to use ES6 Array.from() method). We can then use the array method to filter the links within the page, reuse the same helper function defined above, and finally attach the listener to the remaining link elements.

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.

Accessibility considerations

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="http://ipnx.cn/link/db8229562f80fbcc7d780f571e5974ec"></a>Section 1>>
            <li>></li>
<a> href="http://ipnx.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="http://ipnx.cn/link/e1b97c787a5677efa5eba575c41e8688"></a>a link to another page> ac turpis egestas. <a> href="http://ipnx.cn/link/e1b97c787a5677efa5eba575c41e8688index.html#foo"></a>A link to another page, with an anchor> quam, feugiat vitae, ...>
        <a> href="http://ipnx.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

The CSS Object Model View Module specification introduces a new property to natively implement smooth scrolling:

. scroll-behavior

It can take two values,

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

Can I use css-scroll-behavior? Data from caniuse.com shows the support of css-scroll-behavior functionality by major browsers.

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

Apart from the simplicity and performance implementation, another advantage of the CSS solution just discussed is that the browser history behavior is consistent with the behavior experienced when using the browser's default scrolling. Each in-page jump is pushed to the browser history stack, and we can browse these jumps back and forth with the corresponding buttons (but at least there is no smooth scrolling in Firefox).

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.

FAQs (FAQs) on Smooth Scrolling with Native JavaScript

How to achieve smooth scrolling using native JavaScript without using any libraries?

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.

Why does my smooth scroll not work in Safari?

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.

How to scroll smoothly to a specific element?

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.

Can I control the speed of smooth scrolling?

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.

How to achieve horizontal smooth scrolling?

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.

How to stop smooth scrolling animation?

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.

How to achieve smooth scrolling with fixed headers?

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.

How to use keyboard navigation to achieve smooth scrolling?

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.

How to test the compatibility of my smooth scroll implementation?

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!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undress AI Tool

Undress AI Tool

Undress images for free

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Hot Topics

PHP Tutorial
1488
72
How to make an HTTP request in Node.js? How to make an HTTP request in Node.js? Jul 13, 2025 am 02:18 AM

There are three common ways to initiate HTTP requests in Node.js: use built-in modules, axios, and node-fetch. 1. Use the built-in http/https module without dependencies, which is suitable for basic scenarios, but requires manual processing of data stitching and error monitoring, such as using https.get() to obtain data or send POST requests through .write(); 2.axios is a third-party library based on Promise. It has concise syntax and powerful functions, supports async/await, automatic JSON conversion, interceptor, etc. It is recommended to simplify asynchronous request operations; 3.node-fetch provides a style similar to browser fetch, based on Promise and simple syntax

JavaScript Data Types: Primitive vs Reference JavaScript Data Types: Primitive vs Reference Jul 13, 2025 am 02:43 AM

JavaScript data types are divided into primitive types and reference types. Primitive types include string, number, boolean, null, undefined, and symbol. The values are immutable and copies are copied when assigning values, so they do not affect each other; reference types such as objects, arrays and functions store memory addresses, and variables pointing to the same object will affect each other. Typeof and instanceof can be used to determine types, but pay attention to the historical issues of typeofnull. Understanding these two types of differences can help write more stable and reliable code.

React vs Angular vs Vue: which js framework is best? React vs Angular vs Vue: which js framework is best? Jul 05, 2025 am 02:24 AM

Which JavaScript framework is the best choice? The answer is to choose the most suitable one according to your needs. 1.React is flexible and free, suitable for medium and large projects that require high customization and team architecture capabilities; 2. Angular provides complete solutions, suitable for enterprise-level applications and long-term maintenance; 3. Vue is easy to use, suitable for small and medium-sized projects or rapid development. In addition, whether there is an existing technology stack, team size, project life cycle and whether SSR is needed are also important factors in choosing a framework. In short, there is no absolutely the best framework, the best choice is the one that suits your needs.

JavaScript time object, someone builds an eactexe, faster website on Google Chrome, etc. JavaScript time object, someone builds an eactexe, faster website on Google Chrome, etc. Jul 08, 2025 pm 02:27 PM

Hello, JavaScript developers! Welcome to this week's JavaScript news! This week we will focus on: Oracle's trademark dispute with Deno, new JavaScript time objects are supported by browsers, Google Chrome updates, and some powerful developer tools. Let's get started! Oracle's trademark dispute with Deno Oracle's attempt to register a "JavaScript" trademark has caused controversy. Ryan Dahl, the creator of Node.js and Deno, has filed a petition to cancel the trademark, and he believes that JavaScript is an open standard and should not be used by Oracle

What is the cache API and how is it used with Service Workers? What is the cache API and how is it used with Service Workers? Jul 08, 2025 am 02:43 AM

CacheAPI is a tool provided by the browser to cache network requests, which is often used in conjunction with ServiceWorker to improve website performance and offline experience. 1. It allows developers to manually store resources such as scripts, style sheets, pictures, etc.; 2. It can match cache responses according to requests; 3. It supports deleting specific caches or clearing the entire cache; 4. It can implement cache priority or network priority strategies through ServiceWorker listening to fetch events; 5. It is often used for offline support, speed up repeated access speed, preloading key resources and background update content; 6. When using it, you need to pay attention to cache version control, storage restrictions and the difference from HTTP caching mechanism.

Handling Promises: Chaining, Error Handling, and Promise Combinators in JavaScript Handling Promises: Chaining, Error Handling, and Promise Combinators in JavaScript Jul 08, 2025 am 02:40 AM

Promise is the core mechanism for handling asynchronous operations in JavaScript. Understanding chain calls, error handling and combiners is the key to mastering their applications. 1. The chain call returns a new Promise through .then() to realize asynchronous process concatenation. Each .then() receives the previous result and can return a value or a Promise; 2. Error handling should use .catch() to catch exceptions to avoid silent failures, and can return the default value in catch to continue the process; 3. Combinators such as Promise.all() (successfully successful only after all success), Promise.race() (the first completion is returned) and Promise.allSettled() (waiting for all completions)

Leveraging Array.prototype Methods for Data Manipulation in JavaScript Leveraging Array.prototype Methods for Data Manipulation in JavaScript Jul 06, 2025 am 02:36 AM

JavaScript array built-in methods such as .map(), .filter() and .reduce() can simplify data processing; 1) .map() is used to convert elements one to one to generate new arrays; 2) .filter() is used to filter elements by condition; 3) .reduce() is used to aggregate data as a single value; misuse should be avoided when used, resulting in side effects or performance problems.

JS roundup: a deep dive into the JavaScript event loop JS roundup: a deep dive into the JavaScript event loop Jul 08, 2025 am 02:24 AM

JavaScript's event loop manages asynchronous operations by coordinating call stacks, WebAPIs, and task queues. 1. The call stack executes synchronous code, and when encountering asynchronous tasks, it is handed over to WebAPI for processing; 2. After the WebAPI completes the task in the background, it puts the callback into the corresponding queue (macro task or micro task); 3. The event loop checks whether the call stack is empty. If it is empty, the callback is taken out from the queue and pushed into the call stack for execution; 4. Micro tasks (such as Promise.then) take precedence over macro tasks (such as setTimeout); 5. Understanding the event loop helps to avoid blocking the main thread and optimize the code execution order.

See all articles