Enhance scrolling with a bounce effect

My goal is to implement a smooth scrolling experience with a bounce effect when the user over-scrolls, meaning they scroll too much to the top or bottom. I found an answer on Stack Overflow that explains how to achieve smooth scrolling, but I also want to add a bounce effect when the scroll goes out of bounds. Something similar to this:

Note: The GIF shown here is for mobile, but I want to implement it for all browsers, including desktop and mobile.

https://i.sstatic.net/4zuSZ.gif

I attempted to incorporate the following code into the interval:

// 50 = Padding
if (tgt.scrollTop < 50 || tgt.scrollTop > tgt.scrollHeight - tgt.offsetHeight - 50) {
    pos = Math.bounce(step++, start, change, steps);
} else {
    pos = Math.easeOut(step++, start, change, steps);
}

However, the desired effect is not achieved. The bounce doesn't occur at the correct scrolling time, and it doesn't look realistic enough; it's not large enough.

(I'm aiming for something similar to this effect, but I want to develop it myself and understand the code.)

How can I create a bouncing effect when the scroll reaches the top or bottom?

I'm not looking for answers that lead me to a complex library because I want to create this effect using vanilla JavaScript.

JSFiddle

console.clear();

/* Smooth scroll */
Math.easeOut = function(t, b, c, d) {
  t /= d;
  return -c * t * (t - 2) + b;
};

// Out Back Quartic
Math.bounce = function(t, b, c, d) {
  var ts = (t /= d) * t;
  var tc = ts * t;
  return b + c * (4 * tc + -9 * ts + 6 * t);
};

(function() { // do not mess global space
  var
    interval, // scroll is being eased
    mult = 0, // how fast do we scroll
    dir = 0, // 1 = scroll down, -1 = scroll up
    steps = 50, // how many steps in animation
    length = 30; // how long to animate
  function MouseWheelHandler(e) {
    e.preventDefault(); // prevent default browser scroll
    clearInterval(interval); // cancel previous animation
    ++mult; // we are going to scroll faster
    var delta = -Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
    if (dir != delta) { // scroll direction changed
      mult = 1; // start slowly
      dir = delta;
    }
    for (var tgt = e.target; tgt != document.documentElement; tgt = tgt.parentNode) {
      var oldScroll = tgt.scrollTop;
      tgt.scrollTop += delta;
      if (oldScroll != tgt.scrollTop) break;
    }
    var start = tgt.scrollTop;
    var end = start + length * mult * delta; // where to end the scroll
    var change = end - start; // base change in one step
    var step = 0; // current step
    interval = setInterval(function() {
      var pos;

      // 50 = Padding
      if (tgt.scrollTop < 50 || tgt.scrollTop > tgt.scrollHeight - tgt.offsetHeight - 50) {
        pos = Math.bounce(step++, start, change, steps);
      } else {
        pos = Math.easeOut(step++, start, change, steps);
      }
      tgt.scrollTop = pos;
      if (step >= steps) { // scroll finished without speed up - stop by easing out
        mult = 0;
        clearInterval(interval);
      }

    }, 10);
  }
  var myP = document.getElementById('myP');
  myP.addEventListener("mousewheel", MouseWheelHandler, false);
  //window.addEventListener("DOMMouseScroll", MouseWheelHandler, false);
})();
p {
  height: 300px;
  overflow: auto;
  background-color: orange;
  padding: 50px 0;
}
<p id="myP">Lorem ipsum dolor sit amet consectetuer laoreet faucibus id ut et. Consequat Ut tellus enim ante nulla...
  (Content continues with lorem ipsum text)
  

Answer №1

Greetings! I'm the proud creator of smooth-scrollbar :)

It's time to delve deeper into the intricacies of this question. Brace yourself for a demonstration of overscroll bouncing. See it in action: Demo

We'll be harnessing the power of CSS 3D transform to replicate the bouncing effect, which necessitates a specific DOM structure:

<article class="container">
    <section class="content">
        your content...
    </section>
</article>

Step one involves creating a rendering loop that applies the transform style to .content:

function render() {
    content.style.transform = `translate3d(...)`;

    requestAnimationFrame(render);
}

render();

Next, let's set up two essential state recording variables:

let offset = 0;     // final position
let rendered = 0;   // rendered part

Following that, apply momentum scrolling with a damping factor:

const damping = 0.8;

function render() {
    ...
    const dis = offset - rendered;

    // eliminate decimal part
    const next = offset - (dis * damping | 0);

    content.style.transform = `translate3d(0, ${-next}px, 0)`;
    rendered = next;
    ...
}

Now, we can scroll to the desired offset. But how about springback functionality?

The secret lies in decrementing the offset value during rendering, creating a curve-like effect - ascending to the maximum and then descending back to 0:

function render() {
    ...
    offset = offset / 2 | 0;
    ...
}

So, the updated render function now looks like this:

function render() {
    const dis = offset - rendered;

    // eliminate decimal part
    const next = offset - (dis * damping | 0);

    content.style.transform = `translate3d(0, ${-next}px, 0)`;

    rendered = next;
    offset = offset / 2 | 0;

    requestAnimationFrame(render);
}

Looks much better, doesn't it? The final touch involves handling input events (like wheel or touch). Here's a brief example for wheel events:

// wheel events handler
[
    'wheel',
    'mousewheel'
].forEach(name => {
    container.addEventListener(name, evt => {
        const { y } = getDelta(evt);
        const nextScroll = container.scrollTop + y;

        // check if scrolling to the edge
        if (nextScroll > 0 &&
            nextScroll < container.scrollHeight - container.clientHeight
        ) {
            return;
        }

        evt.preventDefault();

        offset += y;
    });
});

Voilà! A fundamental bouncing model is now at your disposal. For the full code, feel free to visit Codepen: http://codepen.io/idiotWu/pen/EgNdXK.

Nonetheless, there are still numerous issues to address. For instance, the absence of a method to detect when a user lifts their trackpad finger (like touchend event) prevents seamless springback-scrolling when moving away from the trackpad, resulting in potential shaking during overscrolling.

To mitigate shaking, consider implementing a flag to restrict movement incrementation as seen in this example. The core concept is to ignore wheel events during the backward scroll.

===========

Regarding mobile devices, custom touch event handlers are necessary. Remember, scrolling back should only occur upon releasing your fingers, requiring an additional flag to maintain offset stability. Juggling these states can be challenging.

For more insights, refer to smooth-scrollbar/touch.js.

Answer №2

Take a look at this Jquery example that utilizes the mousewheel for functionality. Here are the key steps:

  • Listen for the 'up' mousewheel event
  • Adjust the margin-top of the element and apply an animation with transition
  • Define a maximum value for the offset and a time for reset

Check out the code snippet below:

$(function() {
  var more = 20;
  $('body').on('mousewheel', function(e) {
    if (e.originalEvent.wheelDelta / 120 > 0) {
      var st = $(window).scrollTop();
      if (st == 0 && more < 160) {
        $('div').css({
          'margin-top': more + 'px'
        });
        more += 20
      } else {
        $('div').css({
          'margin-top': '0'
        });
        more = 0;
      }
      setTimeout(function() {
        $('div').css({
          'margin-top': '0'
        });
        more = 0;
      }, 500)
    }
  })
});
body {
  background: #f6f6f6;
  height: 1000px;
  padding-top: 50px;
}
div {
  width: 80%;
  margin: 0 auto;
  background: white;
  height: 400px;
  transition: .3s margin linear;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div></div>


Alternatively, view the JsfiddleDemo

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Regular expression pattern for consistently capitalizing the phrases "CA" and "USA" in an address string

end_address = 'joe's home, 123 test avenue, los angeles, ca, usa 90210'; end_address = end_address.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();}); The outcome of this code will ...

Steps for accessing the "this" keyword within the parent function

Struggling with referencing `this` within a parent function while building a basic tab system using AngularJS. It seems like I may not have a solid grasp on the fundamentals, so any guidance would be appreciated: JavaScript: $scope.tabs = { _this: th ...

How to update a value within a deeply nested array in MongoDB and then sort the data

In my document, I have a list of timestamps that are sorted by time: { _id: '1', timestamps: [ { id: '589b32cf-28b3-4a25-8fd1-5e4f86682199', time: '2022-04-13T19:00:00.122Z' }, { id: '781 ...

Executing a npm script (script.js) with custom arguments - a step-by-step guide

I'm considering creating a setup similar to lodash custom builds. Basically, I want to allow users to input a command like: lodash category=collection,function This would create a custom module with the specified category. I've been looking in ...

Issue with toggling JS checkboxes selection

Seeking assistance in expanding my knowledge of Javascript. I have a form with checkboxes and a JavaScript function that allows toggling between selecting and deselecting all the boxes. Everything is functioning as intended so far. The challenge arises f ...

CSS - Combining underline, striikethrough, and overline effects with customized styles and colors within a single element

I am trying to achieve a unique design with only one <span> that has three different text decorations (underline, strikethrough, and overline) like this: (This is just an example, I need it to be changeable) https://i.stack.imgur.com/61ZnQ.png Ho ...

What is the best way to design a webpage that adapts to different screen heights instead of widths?

I'm in the process of designing a basic webpage for a game that will be embedded using an iframe. The game and text should always adjust to fit the height of your screen, so when the window is small, only the game is displayed. The game will be e ...

Setting the texture for a loaded glb model using Three.js

After successfully loading a basic glb model created in SketchUp using Three.JS, I encountered an issue with displaying text on the model. The model includes a group identified as Text. Despite being able to load and visualize the model correctly in Three ...

There is no way to convert a strongly typed object into an observable object using MobX

I have been utilizing the MobX library in conjunction with ReactJS, and it has integrated quite smoothly. Currently, I am working with an observable array structured as follows: @observable items = []; When I add an object in the following manner, everyt ...

Ways to retrieve the parent DIV's width and adjust the child DIV's width to match

Attached is my question with the HTML and a screenshot. Within a DIV container (DIV with ID=ctl00_m_g_a788a965_7ee3_414f_bff9_2a561f8ca37d_ctl00_pnlParentContainer), there are two inner DIVs - one for the left column TITLE (DIV ID=dvTitles) and the other f ...

Encountering a critical issue with Angular 12: FATAL ERROR - The mark-compacts are not working effectively near the heap limit, leading to an allocation failure due

After upgrading my Angular application from version 8 to 12, I encountered an issue. Previously, when I ran ng serve, the application would start the server without any errors. However, after updating to v12, I started receiving an error when I attempted t ...

What is the best way to showcase the dropdown menu items of a bootstrap navbar in a horizontal layout instead of vertical?

While utilizing Bootstrap 4, I encountered an issue with the dropdown section where the items are displayed vertically instead of horizontally. I want these dropdown items to be arranged horizontally next to each other. <nav class="navbar navbar-expand ...

Positioning Elements at the Bottom with Bootstrap 5

Here are the codes you requested: <div class="d-flex"> <h1>Hello World</h1> <p class="ms-auto small">Stupid Text</p> </div> <hr class="my-0"> I am attempting to vertically ali ...

Modifying computed object in Vue: A step-by-step guide

My issue involves a simple selection of months: <select v-model="month" v-on:change="dateChanged('month', month)"> <option v-for="(month, index) in months" :value="index">{{ month }}</option> </select> The primary da ...

Ways to prevent the "RangeError: Maximum call stack size exceeded" error

Currently, I am working on developing a maze generating algorithm known as recursive division. This algorithm is relatively straightforward to comprehend: Step 1 involves dividing the grid/chamber with a vertical line if the height is less than the width, ...

What is the standard text displayed in a textarea using jQuery by default

My goal is to display default text in a textarea upon page load. When the user clicks on the textarea, I want the default text to disappear. If the user clicks into the textarea without typing anything and then clicks out of it, I'd like the default t ...

What is the best way to move between websites or pages without having to reload the current page using a selector?

Have you ever wondered how to create a webpage where users can navigate to other websites or pages without seeing their address, simply by selecting from a drop-down menu? Take a look at this example. Another similar example can be found here. When visit ...

What is the method for defining the maximum selectable month in mtz.monthpicker?

(edited) Currently, I am utilizing the jquery.mtz.monthpicker plugin along with jquery. My goal is to limit the selection of future months, but it appears that there are no options similar to 'maxDate' like in jquery.ui.datepicker. $('inp ...

Having difficulty deleting an entry from a flatList in a React Native component when using the filter method

I'm currently facing an issue with deleting an item from my flatlist in React Native. I've been attempting to use the filter method to exclude the list item with the ID entered by the user for deletion, but it's not working as expected. I&ap ...

Tips for creating animated clouds that move across the screen using Flash, jQuery, or JavaScript

Are there methods to create scrolling clouds using tools such as Flash, jQuery or JavaScript? If so, can you provide me with the code required to implement this on my homepage? I have three small cloud images that I would like to animate moving in the bac ...