Author: Bump man – Acridis

We are programming development at ordinary times, in addition to the need to focus on technology, algorithms, and code efficiency factors, more knowledge you’ve learned to flexible application (such as physics, theoretical mathematics, etc.), the theory and practice, after all, complement each other, inseparable, this for our scheme selection, or technical practice understanding has very great help. Today let us review the middle school physics knowledge, and flexible use of inertial rolling in the implementation of the dynamic effect.

Inertial scrolling (also known as “moment-based scrolling”) first appeared in iOS. When the user swipes on a page and moves his or her finger away from scrolling, the page doesn’t stop immediately but scrolling for a certain amount of time. And the speed and duration of the scroll were proportional to the intensity of the gesture. In an abstract sense, it’s like a high-speed train that stops for some distance before finally coming to a stop. And on iOS, it’s possible to trigger a “bounce back” effect when scrolling to the top/bottom of the page. Here is the inertial scrolling effect of iOS native time selector in the wechat APP [Bill] page:

If you’re familiar with CSS development, you might know that Safari has this CSS rule:

-webkit-overflow-scrolling: touch;
Copy the code

When the style value is touch, the browser uses a scroll with a rebound effect, meaning that “when the finger is removed from the touch screen, the content continues to scroll for a period of time.” In addition, in the rich web front-end ecology, many classic component interactions use the effect of inertial scrolling to some extent, such as the examples in the popular H5 component libraries mentioned below.

Popular UI library effects

For comparison, let’s take a look at the scrolling performance of a H5 common long list in iOS (with scroll rebound enabled) :

  • The Picker component of weui

    Obviously, the inertial scrolling effect of the WeUI selector is very weak, and the scrolling stops very quickly after the basic start is removed from the screen, which is a poor experience.

  • Vant picker component

    In contrast, the vant selector’s inertial roll effect is much sharper, but the overall rebound effect is slightly disjointed because the top/bottom rebound still maintains the same coefficient or duration as the normal roll.

Applied physics model

The term inertia comes from the law of inertia in physics (namely Newton’s first law) : all bodies do not change their state of motion without being acted upon by a force. This property is called inertia. It can be imagined that the essence of inertial rolling is the inertial phenomenon in physics. Therefore, we can properly use the slider model in middle school physics to describe the whole process of inertial rolling.

For the sake of description, we simulate the scrolling objects in the browser inertial scrolling effect (such as page elements in the browser) as the sliders in the slider model. Moreover, the analysis shows that the whole process of inertial rolling can be simulated as the process of (people) making the slider slide a certain distance and then release. Then, the whole process can be disassembled into the following two stages:

  • In the first stage, the sliding block causes it to accelerate from rest;

    At this stage, the slider is pulled by F greater than F mo and accelerates evenly from left to right.

    It is important to note that in the case of inertial scrolling in the browser, we generally focus on the small phase before the user is about to release the finger, rather than the whole process of scrolling (the whole process is not meaningful), which can be simply simulated as a uniform acceleration of the slider under equal force.

  • In the second stage, the slider is released so that it continues to slide under friction only until it finally comes to rest.

    At this stage, the slider is only subjected to the opposite friction force, and will continue to slow down in the left to right direction and then stop.

Based on the slider model, we need to find a suitable quantitative index to establish the inertial rolling calculation system. In combination with the model and concrete implementation, we need to pay attention to the key indicators such as rolling distance, velocity curve and rolling duration, which will be expanded and analyzed one by one below.

The rolling distance

For the first stage of the sliding model, the slider moves in uniform acceleration, we may as well set the sliding distance of the slider as S1, the sliding time as T1, and the critical point velocity (final velocity) at the end as V1, according to the displacement formula

We can get the velocity relationship

In the second stage, the sliding block is pulled by the friction force F to make uniform deceleration motion. We may as well set the sliding distance as S2, the sliding time as T2, the sliding acceleration as A, the initial velocity as V1, and the final velocity as 0m/s, combining the displacement formula and acceleration formula

The sliding distance S2 can be calculated

Since the acceleration of uniform deceleration motion is negative (that is, a < 0), it may as well set an acceleration constant A to satisfy the relation a = -2a, then the sliding distance

However, in the actual browser application, v1 squared will result in the final inertial scroll distance (i.e. too sensitive to the strength of the scroll gesture), we may as well remove the square operation:

Therefore, when finding the inertial rolling distance (s2), we only need to record the user’s rolling distance s1 and rolling time T1, and set an appropriate acceleration constant A.

A large number of tests show that the appropriate value of acceleration constant A is 0.003.

In addition, it should be noted that for real browser inertial scrolling, the distance and duration discussed here refer to the distance and duration that can be applied to the inertial scrolling, not the entire process of the user scrolling the page elements. See “Start/Stop Conditions” for more details.

Inertial rolling velocity curve

For the inertial rolling phase, that is, the uniform deceleration motion in the second phase, the relationship between the displacement difference and the time interval T can be obtained according to the displacement formula

It is not difficult to conclude that, under the condition of the same time interval, the displacement difference between the two adjacent segments will become smaller and smaller. In other words, the velocity will become smaller and smaller as the offset of inertial rolling increases. This is very consistent with the Ease-out velocity curve in CSS3 Transition-timing function, and the Ease-out (i.e., cubic- Bezier (0, 0,.58, 1)) is

The graph is from the Website Draw Bezier Curves online.

In the chart, the ordinate refers to the progress of the animation, and the abscess refers to the time. The origin coordinate is (0, 0) and the end coordinate is (1, 1). Assuming the duration of the animation is 2 seconds, the coordinate point (1, 1) represents the completion of the animation (100%) 2 seconds after the animation starts. According to the chart, the further back the time goes, the slower the animation progresses, which conforms to the characteristics of uniform deceleration.

Let’s try the practical application Ease-out Speed Curve:

Obviously, such a speed curve is too linear smooth, the effect of deceleration is not obvious. We repeated the test by referring to the effect of iOS roll rebound and adjusted the parameters of the Bezier curve to Cubic – Bezier (.17,.89,.45, 1) :

The effect after adjusting the curve is much better:

The springback

Next, we simulate an inertial roll in which a container boundary is touched to trigger a springback.

We simulate the following scenario based on the slider model: Slider on the left side is connected with a spring, a spring at the other end fixed on the wall, in the process of slide the slider to the right, when slider reaches critical point (deformation) spring is about to happen and the speed is not down to 0 m/s, the slider will continue to slide and pull the spring deformation, make it happen at the same time, the slider will be affected by the spring counter loop works slow movement (kinetic energy into internal energy); When the speed of the slider decreases to 0m/s, the shape variable of the spring is the maximum. Due to its elastic characteristics, the spring will return to its original state (internal energy is converted into kinetic energy) and pull the slider to move in reverse (left).

Similarly, the springback process can be divided into two stages:

  • The slider pulls the spring to the right to do variable deceleration;

At this stage, the slider is subjected to the joint action of the friction force F friction and the increasing spring tension F projectile, and the acceleration becomes larger and larger, resulting in a very short time for the velocity to drop to 0m/s.

  • Spring returns to its original state, pull the slider to the left to do first change the acceleration and then change the deceleration movement;

    At this stage, the friction force F friction and the smaller and smaller spring tension F elastic cancel each other. In the beginning, F elastic > F friction, the slider makes the variable acceleration motion with smaller and smaller acceleration; Then F < F friction, the slider to do more and more acceleration of the deceleration movement, until finally static. For the sake of practical calculation, let us assume an ideal state in which the spring recovers exactly when the slider is at rest.

The springback distance

According to the above model analysis, the first stage of springback is a linear motion of variable deceleration with increasing acceleration. Let’s set the initial velocity of this stage as V0 and the final velocity as V1, then a relationship can be established with the slide displacement:

Where a is the acceleration variable, which will not be discussed here. Then, according to the elastic model of physics, the springback distance of the second stage is

Calculus is here, it’s impossible to calculate…

However, we can simplify the calculation of the S springback value according to the motion model. Since the acceleration of the second stage of springback is greater than the acceleration of the non-springback inertial rolling stage (F spring + F friction > F friction), it may as well set the total distance of the non-springback inertial rolling stage as S slip, then

Therefore, we can set a reasonable constant B that satisfies the following equation:

After a lot of practice, the reasonable value of constant B is 10.

Rebound velocity curve

The entire inertial rolling track that triggers the springback can be divided into three phases of motion:

However, it is quite complicated to accurately depict stage A and B as CSS animations:

  • phasebThe variable deceleration motion in is difficult to describe accurately;
  • Although the movement direction of these two stages is the same, the animation speed curve is not consistent, which is easy to cause the fault of user experience.

In order to simplify the process, we combine phase A and B into one motion phase, and then the simplified trajectory becomes:

Since the reverse acceleration at the end of stage A will be greater and greater, the speed of the slide in this stage will be reduced faster than the non-springback inertia roll, and the corresponding Bessel curve will end steeper. We choose a more reasonable curve cubic- Bezier (.25,.46,.45,.94) :

For stage B, the slider changes from acceleration to deceleration, which is similar to the ease-in-out curve. In practice, try:

Upon careful observation, we find that the connection between Stage A and Stage B is not smooth enough, which is caused by the easing in of the first half of the ease-in-out curve. Therefore, to highlight the effect we chose to depict only the end of phase B of the variable deceleration motion. Cubic curves are adjusted to Cubic – Bezier (.165,.84,.44, 1)

Practical results:

Due to the GIF format caused by partial frame drop, the sample effect seems to be a little slow, it is recommended to directly experience the demo

CSS action duration

We measured the scrolling rebound of iOS several times, and defined the parameters of the duration of the action that had a good experience. In an inertial roll, the following two situations may occur, and the corresponding dynamic effect time is different:

  • No rebound was triggered

    A reasonable duration of inertial rolling is 2500ms.

  • Trigger a rebound

    For phase A, when the springback of S is greater than a certain critical threshold, it is defined as a strong springback, and the dynamic effect duration is 400ms. Otherwise, it is defined as weak springback, and the dynamic effect duration is 800ms.

    For stage B, the rebound duration of 500ms is reasonable.

Start-stop condition

As mentioned earlier, it would be unreasonable to count the entire process of a user scrolling through a page element. It’s not hard to imagine a user rolling an element over a large distance at a very slow speed, in which case the element momentum is so small that inertial scrolling should not be triggered. Therefore, the trigger of inertial rolling is conditional.

  • Start conditions

    Inertial rolling requires sufficient momentum to start. We can simply assume that inertial scrolling occurs when the distance the user scrolls is large enough (greater than 15px) and the duration is short enough (less than 300ms). In the programming language, if the interval between the last TouchMove event firing and the Touchend event firing is less than 300ms, and the difference between the two is greater than 15px, it is considered to initiate inertial scrolling.

  • Pause time

    When the user touches the scrolling element again while the inertial scrolling has not finished (including when it is in the rebound process), we should pause the scrolling of the element. In principle, we need to get the current transform: matrix() matrix value through the getComputedStyle and getPropertyValue methods, pull out the horizontal Y-axis offset of the element and readjust the translate position.

The sample code

Some of the key code is provided based on vuejs, and you can also directly access the Codepen Demo to experience the effects (complete code).

<html>
  <body>
    <div id="app"></div>
    <template id="tpl">
      <div
        ref="wrapper"
        @touchstart.prevent="onStart"
        @touchmove.prevent="onMove"
        @touchend.prevent="onEnd"
        @touchcancel.prevent="onEnd"
        @transitionend="onTransitionEnd">
        <ul ref="scroller" :style="scrollerStyle">
          <li v-for="item in list">{{item}}</li>
        </ul>
      </div>
    </template>
    <script>
      new Vue({
        el: '#app'.template: '#tpl'.computed: {
          list() {},
          scrollerStyle() {
            return {
              'transform': `translate3d(0, The ${this.offsetY}px, 0)`.'transition-duration': `The ${this.duration}ms`.'transition-timing-function': this.bezier,
            };
          },
        },
        data() {
          return {
            minY: 0.maxY: 0.wrapperHeight: 0.duration: 0.bezier: 'linear'.pointY: 0.// touchStart gesture y coordinate
            startY: 0.// touchStart element y offset
            offsetY: 0.// The real-time y offset of the element
            startTime: 0.// startTime within the inertia sliding range
            momentumStartY: 0.// Inertial sliding range within the startY
            momentumTimeThreshold: 300.// Start time threshold for inertial sliding
            momentumYThreshold: 15.// The starting distance threshold for inertial sliding
            isStarted: false./ / start the lock
          };
        },
        mounted() {
          this.$nextTick((a)= > {
            this.wrapperHeight = this.$refs.wrapper.getBoundingClientRect().height;
            this.minY = this.wrapperHeight - this.$refs.scroller.getBoundingClientRect().height;
          });
        },
        methods: {
          onStart(e) {
            const point = e.touches ? e.touches[0] : e;
            this.isStarted = true;
            this.duration = 0;
            this.stop();
            this.pointY = point.pageY;
            this.momentumStartY = this.startY = this.offsetY;
            this.startTime = new Date().getTime();
          },
          onMove(e) {
            if (!this.isStarted) return;
            const point = e.touches ? e.touches[0] : e;
            const deltaY = point.pageY - this.pointY;
            this.offsetY = Math.round(this.startY + deltaY);
            const now = new Date().getTime();
            // Record the offset value and time for triggering the inertial sliding condition
            if (now - this.startTime > this.momentumTimeThreshold) {
              this.momentumStartY = this.offsetY;
              this.startTime = now;
            }
          },
          onEnd(e) {
            if (!this.isStarted) return;
            this.isStarted = false;
            if (this.isNeedReset()) return;
            const absDeltaY = Math.abs(this.offsetY - this.momentumStartY);
            const duration = new Date().getTime() - this.startTime;
            // Start inertial sliding
            if (duration < this.momentumTimeThreshold && absDeltaY > this.momentumYThreshold) {
              const momentum = this.momentum(this.offsetY, this.momentumStartY, duration);
              this.offsetY = Math.round(momentum.destination);
              this.duration = momentum.duration;
              this.bezier = momentum.bezier;
            }
          },
          onTransitionEnd() {
            this.isNeedReset();
          },
          momentum(current, start, duration) {
            const durationMap = {
              'noBounce': 2500.'weekBounce': 800.'strongBounce': 400};const bezierMap = {
              'noBounce': 'cubic-bezier(.17, .89, .45, 1)'.'weekBounce': 'cubic-bezier(.25, .46, .45, .94)'.'strongBounce': 'cubic-bezier(.25, .46, .45, .94)'};let type = 'noBounce';
            // The inertial sliding acceleration
            const deceleration = 0.003;
            // Rebound resistance
            const bounceRate = 10;
            // The split value of strong and weak springback
            const bounceThreshold = 300;
            // Maximum springback
            const maxOverflowY = this.wrapperHeight / 6;
            let overflowY;

            const distance = current - start;
            const speed = 2 * Math.abs(distance) / duration;
            let destination = current + speed / deceleration * (distance < 0 ? - 1 : 1);
            if (destination < this.minY) {
              overflowY = this.minY - destination;
              type = overflowY > bounceThreshold ? 'strongBounce' : 'weekBounce';
              destination = Math.max(this.minY - maxOverflowY, this.minY - overflowY / bounceRate);
            } else if (destination > this.maxY) {
              overflowY = destination - this.maxY;
              type = overflowY > bounceThreshold ? 'strongBounce' : 'weekBounce';
              destination = Math.min(this.maxY + maxOverflowY, this.maxY + overflowY / bounceRate);
            }

            return {
              destination,
              duration: durationMap[type],
              bezier: bezierMap[type],
            };
          },
          // The position needs to be reset when the boundary is exceeded
          isNeedReset() {
            let offsetY;
            if (this.offsetY < this.minY) {
              offsetY = this.minY;
            } else if (this.offsetY > this.maxY) {
              offsetY = this.maxY;
            }
            if (typeofoffsetY ! = ='undefined') {
              this.offsetY = offsetY;
              this.duration = 500;
              this.bezier = 'cubic-bezier(.165, .84, .44, 1)';
              return true;
            }
            return false;
          },
          // Stop scrolling
          stop() {
            const matrix = window.getComputedStyle(this.$refs.scroller).getPropertyValue('transform');
            this.offsetY = Math.round(+matrix.split(') ') [0].split(', ') [5]); ,}}});</script>
  </body>
</html>
Copy the code

The resources

  • weui-picker
  • better-scroll

Welcome to the Bump Lab blog: aotu.io

Or follow the public account of AOTULabs and push articles from time to time: