Mobile custom scroll bar

Keywords: Android Mobile

In the development of mobile terminal, in order to prevent the click through behavior, we sometimes block the default behavior of the browser. At this time, we need to customize the required browser behavior. This article introduces the scroll bar of the customized browser.

Default behavior of mobile browser

The default behavior of browser m is as follows:

  • Scroll bar up and down
  • Long press to select text and picture
  • Input box get focus
  • User scaling
  • Simulate click action (click through)
  • Sliding blank area
  • ...

Tween algorithm

Because it is difficult to realize the point and stop effect of scroll bar with conventional css transition and timing, we need to introduce tween algorithm To achieve the corresponding effect.

Parameter Description:
t: current time: current time;
b: beginning value: initial value, the initial position of the element;
c: change in value: change amount, the distance between the end position and the initial position of the element
 d: duration: duration, the whole transition duration
 s: optional parameters for Elastic and Back. The larger the springback coefficient is, the farther the springback effect is.
‚Äč
Return value:
Where the element moves every time
/*tween class*/
var Tween = {
    Linear: function(t,b,c,d){ return c*t/d + b; },
    Quad: {
        easeIn: function(t,b,c,d){
            return c*(t/=d)*t + b;
        },
        easeOut: function(t,b,c,d){
            return -c *(t/=d)*(t-2) + b;
        },
        easeInOut: function(t,b,c,d){
            if ((t/=d/2) < 1) return c/2*t*t + b;
            return -c/2 * ((--t)*(t-2) - 1) + b;
        }
    },
    Cubic: {
        easeIn: function(t,b,c,d){
            return c*(t/=d)*t*t + b;
        },
        easeOut: function(t,b,c,d){
            return c*((t=t/d-1)*t*t + 1) + b;
        },
        easeInOut: function(t,b,c,d){
            if ((t/=d/2) < 1) return c/2*t*t*t + b;
            return c/2*((t-=2)*t*t + 2) + b;
        }
    },
    Quart: {
        easeIn: function(t,b,c,d){
            return c*(t/=d)*t*t*t + b;
        },
        easeOut: function(t,b,c,d){
            return -c * ((t=t/d-1)*t*t*t - 1) + b;
        },
        easeInOut: function(t,b,c,d){
            if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
            return -c/2 * ((t-=2)*t*t*t - 2) + b;
        }
    },
    Quint: {
        easeIn: function(t,b,c,d){
            return c*(t/=d)*t*t*t*t + b;
        },
        easeOut: function(t,b,c,d){
            return c*((t=t/d-1)*t*t*t*t + 1) + b;
        },
        easeInOut: function(t,b,c,d){
            if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
            return c/2*((t-=2)*t*t*t*t + 2) + b;
        }
    },
    Sine: {
        easeIn: function(t,b,c,d){
            return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
        },
        easeOut: function(t,b,c,d){
            return c * Math.sin(t/d * (Math.PI/2)) + b;
        },
        easeInOut: function(t,b,c,d){
            return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
        }
    },
    Expo: {
        easeIn: function(t,b,c,d){
            return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
        },
        easeOut: function(t,b,c,d){
            return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
        },
        easeInOut: function(t,b,c,d){
            if (t==0) return b;
            if (t==d) return b+c;
            if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
            return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
        }
    },
    Circ: {
        easeIn: function(t,b,c,d){
            return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
        },
        easeOut: function(t,b,c,d){
            return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
        },
        easeInOut: function(t,b,c,d){
            if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
            return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
        }
    },
    Elastic: {
        easeIn: function(t,b,c,d,a,p){
            if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
            if (!a || a < Math.abs(c)) { a=c; var s=p/4; }
            else var s = p/(2*Math.PI) * Math.asin (c/a);
            return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
        },
        easeOut: function(t,b,c,d,a,p){
            if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
            if (!a || a < Math.abs(c)) { a=c; var s=p/4; }
            else var s = p/(2*Math.PI) * Math.asin (c/a);
            return (a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b);
        },
        easeInOut: function(t,b,c,d,a,p){
            if (t==0) return b;  if ((t/=d/2)==2) return b+c;  if (!p) p=d*(.3*1.5);
            if (!a || a < Math.abs(c)) { a=c; var s=p/4; }
            else var s = p/(2*Math.PI) * Math.asin (c/a);
            if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
            return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
        }
    },
    Back: {
        easeIn: function(t,b,c,d,s){
            if (s == undefined) s = 1.70158;
            return c*(t/=d)*t*((s+1)*t - s) + b;
        },
        easeOut: function(t,b,c,d,s){
            if (s == undefined) s = 1.70158;
            return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
        },
        easeInOut: function(t,b,c,d,s){
            if (s == undefined) s = 1.70158; 
            if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
            return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
        }
    },
    Bounce: {
        easeIn: function(t,b,c,d){
            return c - Tween.Bounce.easeOut(d-t, 0, c, d) + b;
        },
        easeOut: function(t,b,c,d){
            if ((t/=d) < (1/2.75)) {
                return c*(7.5625*t*t) + b;
            } else if (t < (2/2.75)) {
                return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
            } else if (t < (2.5/2.75)) {
                return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
            } else {
                return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
            }
        },
        easeInOut: function(t,b,c,d){
            if (t < d/2) return Tween.Bounce.easeIn(t*2, 0, c, d) * .5 + b;
            else return Tween.Bounce.easeOut(t*2-d, 0, c, d) * .5 + c*.5 + b;
        }
    }
}

Use cases

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        #outer {
            margin: 50px;
            width: 600px;
            height: 40px;
            border: 5px solid green;
        }
        #inner {
            width: 0;
            height: 40px;
            background: #f90;
        }
        .btn{
            margin-top: 20px;
        }
    </style>
</head>
<body>

    <div id="outer">
        <div id="inner"></div>
        <div class="btn">
            <button class="btn1">Uniform speed</button>
            <button class="btn2">Slow down</button>
            <button class="btn3">springback</button>
        </div>
    </div>
    <script>
        //Define the function of Tween algorithm
        var tween = {
            //Uniform speed
            linear: function(t,b,c,d){ return c*t/d + b; },
            //Slow down
            cubicEaseOut: function(t,b,c,d){
                return c*((t=t/d-1)*t*t + 1) + b;
            },
            //springback
            backEaseOut: function(t,b,c,d,s){
                if (s === undefined) s = 1.70158;
                return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
            },
        };

        /*
        *  Tween Parameters of function
        *    t   current time  current time
        *    b   beginning value Initial value
        *    c   change in value  Variable amount of value (target initial value)
        *    d   duration    Duration
        *    s   Resilience coefficient
        * */

        var inner = document.querySelector('#inner');
        var btn1 = document.querySelector('.btn1');
        var btn2 = document.querySelector('.btn2');
        var btn3 = document.querySelector('.btn3');



        btn1.onclick = function () {
            var t = 0;
            var b = 0;
            var c = 600;
            var d = 5000;
            var intervalId = setInterval(function(){
                t += 10;
                var value = tween.linear(t, b, c, d); //Uniform speed
                inner.style.width = value + 'px';
                // If the current time point has reached the total duration, listen
                if (t >= d) {
                    clearInterval(intervalId);
                }
            }, 10);
        };
        btn2.onclick = function () {
            var t = 0;
            var b = 0;
            var c = 600;
            var d = 5000;
            var intervalId = setInterval(function(){
                t += 10;
                var value = tween.cubicEaseOut(t, b, c, d); //Slow down
                inner.style.width = value + 'px';
                // If the current time point has reached the total duration, listen
                if (t >= d) {
                    clearInterval(intervalId);
                }
            }, 10);
        };
        btn3.onclick = function () {
            var t = 0;
            var b = 0;
            var c = 600;
            var d = 5000;
            var intervalId = setInterval(function(){
                t += 10;
                var value = tween.backEaseOut(t, b, c, d); //springback
                inner.style.width = value + 'px';
                // If the current time point has reached the total duration, listen
                if (t >= d) {
                    clearInterval(intervalId);
                }
            }, 10);
        };

    </script>
</body>
</html>

Customize screen scroll bar

//Custom screen scrolling plug-in touchScroll.js
(function(w){
    /**
     * Realize screen scrolling
     * @param wrapper   Wrapped elements
     * @param content   Content elements (responsible for location changes)
     * @param scrollBar Scroll bar element; optional; if not specified, no scroll bar
     */
    function touchScroll(wrapper, content, scrollBar){
        var intervalId = null;  //Defining timer markers

        //Calculate the height of the scroll bar
        if (scrollBar) {
            var scale1 = wrapper.clientHeight / content.offsetHeight; //Proportion
            scrollBar.style.height = wrapper.clientHeight * scale1 + 'px';
        }

        //Turn on 3d acceleration
        transformCss(content, 'translateZ', 0);

        //Touch start
        wrapper.addEventListener('touchstart', function(event){
            //Get contact object
            var touch = event.targetTouches[0];
            // Get the starting position of the contact
            this.startY = touch.clientY;
            // Get the starting position of content
            this.eleY = transformCss(content, 'translateY');
            // Initial contact sliding distance
            this.dstY = 0;
            //Initial time
            this.startTime = Date.now();

            //scroll bars displaying
            if (scrollBar) {
                scrollBar.style.opacity = 1;
            }
            //Cancel timer
            clearInterval(intervalId);
        });
        //Touch Mobile
        wrapper.addEventListener('touchmove', function(event){
            //Get contact object
            var touch = event.targetTouches[0];
            //Get the end position of the contact
            var endY = touch.clientY;
            //Calculate the sliding distance of the contact
            this.dstY = endY - this.startY;

            // Calculate the position of content according to the sliding distance
            var translateY = this.eleY + this.dstY;

            //Judge the critical value and open the rubber band effect
            if (translateY >= 0) { //Upper boundary
                //Calculation proportion
                var scale = 1 - translateY / (wrapper.clientHeight * 1.9);
                //Recalculate translateY
                translateY *= scale;
            } else if (translateY <= (wrapper.clientHeight - content.offsetHeight)) {
                //Calculate the distance between content and the bottom of the viewport
                var bottomY =  wrapper.clientHeight - (content.offsetHeight + translateY);
                // Calculation proportion
                var scale = 1 - bottomY / (wrapper.clientHeight * 1.9);
                // Recalculate bottom
                bottomY *= scale;
                // Recalculate translateY
                translateY = (wrapper.clientHeight - bottomY) - content.offsetHeight
            }

            // Set the location of content
            transformCss(content, 'translateY', translateY);

            // Adjust scroll bar position
            if (scrollBar) {
                setScrllBarOffset(translateY);
            }
        });

        //End of touch
        wrapper.addEventListener('touchend', function(event){
            //End time
            var dstTime = Date.now() - this.startTime;
            //Calculate acceleration distance
            var speed = this.dstY / dstTime * 200;

            // Calculate the position of the element at this time and add the acceleration distance
            var translateY = transformCss(content, 'translateY');
            translateY += speed;

            //Set transition type
            var type = 'cubicEaseOut';  //It's slowing down all the time

            //Judge to reach critical point and start rebound
            if (translateY >= 0) {
                translateY = 0;
                type = 'backEaseOut';
            } else if (translateY <= wrapper.clientHeight - content.offsetHeight) {
                translateY = wrapper.clientHeight - content.offsetHeight;
                type = 'backEaseOut';
            }

            //Reset content location to call transition function
            moveTo(content, translateY, 500, type);
        });

        /**
         * Transition function
         * @param node  Elements to transform
         * @param target    target value
         * @param duration      Transition duration
         * @param type  Transition types linear, cubicEaseOut, backEaseOut
         */
        function moveTo(node, target, duration, type = 'linear') {

            //Define the function of Tween algorithm
            var tween = {
                //Uniform speed
                linear: function(t,b,c,d){ return c*t/d + b; },
                //Slow down
                cubicEaseOut: function(t,b,c,d){
                    return c*((t=t/d-1)*t*t + 1) + b;
                },
                //springback
                backEaseOut: function(t,b,c,d,s){
                    if (s === undefined) s = 1.70158;
                    return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
                },
            };

            // Define parameters of Tween function
            var t = 0;  //current time
            var b = transformCss(node, 'translateY');  //Starting value
            var c = target - b;  //Amount of change
            var d = duration;

            //Avoid multiple timings, and clear the previous timings (whether there is one or not)
            clearInterval(intervalId);
            //Opening timing
            intervalId = setInterval(function(){
                //t transformation
                t += 10;
                //Using Tween function to get the current value
                var translateY = tween[type](t,b,c,d);
                // Set the location of content
                transformCss(node, 'translateY', translateY);

                //Adjust the position of the scroll bar
                if (scrollBar) {
                    setScrllBarOffset(translateY);
                }

                //Judge transition completed
                if (t >= d) {
                    //Timed end transition end
                    clearInterval(intervalId);
                    //Scroll bar hide
                    if (scrollBar) {
                        scrollBar.style.opacity = 0;
                    }
                }
            }, 10);
        }

        /**
         * Set scroll bar position
         * @param contentTranslateY Where the content is now
         */
        function setScrllBarOffset(contentTranslateY) {
            // Calculate proportion content current position / maximum position
            var scale2 = -contentTranslateY / (content.offsetHeight - wrapper.clientHeight);
            // Calculate scroll bar position
            transformCss(scrollBar, 'translateY', (wrapper.clientHeight - scrollBar.offsetHeight) * scale2);
        }
    }

    //Expose the method
    w.touchScroll = touchScroll;
})(window);

Use case - vertical slide

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0,viewport-fit:cover">
    <title>Custom vertical slide</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        html,body,#app {
            width:100%;
            height: 100%;
            overflow: hidden;
        }
        #content {
            font-size: 26px;
        }
        #scrollBar {
            position: absolute;
            top: 0;
            right: 0;
            width: 4px;
            height: 100px;
            border-radius: 2px;
            background-color: rgba(0,0,0,.8);
            opacity: 0;
            transition: opacity .2s;
        }
    </style>
</head>
<body>
    <div id="app">
        <div id="content"></div>
        <div id="scrollBar"></div>
    </div>

    <script src="js/transformcss.js"></script>
    <script src="js/touchscroll.js"></script>
    <script>
        (function () {
            var app = document.querySelector('#app');
            var content = document.querySelector('#content');
            var scrollBar = document.querySelector('#scrollBar');

            app.addEventListener('touchstart', function(event){
                event.preventDefault();
            });

            for (var i = 0; i <= 200; i ++) {
                content.innerHTML += i + '<br>';
            }
            touchScroll(app, content, scrollBar);
        })();
    </script>
</body>
</html>

Note: in order to realize touch movement more conveniently and concisely, transformcss.js file is used when customizing the scroll bar (touchScroll.js). Therefore, transformcss.js should be introduced before using the custom scroll bar plug-in. Address: https://segmentfault.com/a/11... , or go directly to my last article: encapsulating the transfrom function.

Note: the author is just a novice who gropes silently on the front road. If there are any mistakes in this article, please correct them in time. If this article is helpful to you, click brother Ming's website http://learn.fuming.site/ Learn more!

Posted by jdwmk on Thu, 05 Dec 2019 13:24:15 -0800