Создайте виртуальную полосу прокрутки на основе vue.js 2.x

JavaScript Vue.js

предисловие

Я помню, как однажды случайно просматривал проект cms с открытым исходным кодом и обнаружил, что меню слева от этого проекта превышает ширину окна, и мне стало интересно, почему нет полосы прокрутки? Затем я присмотрелся и увидел небольшой div слева от него, а затем попытался перетащить его и обнаружил, что это то же самое, что и родная полоса прокрутки! Просмотрев его исходный код, я обнаружил, что эта полоса прокрутки называется slimScroll, а затем перешел к еерепозиторий githubПрочитав ее, я изучил исходный код, и это дает мне ощущение, что я могу сделать такую ​​же полосу прокрутки! Реализовано с помощью vue!

дизайн

Хорошо, теперь давайте приступим к разработке полосы прокрутки:

Дизайн полосы прокрутки

Первое, о чем стоит подумать, это:Если вы хотите прокручивать контент, который вам нужно прокручивать, первый момент заключается в том, что его родительский dom должен быть фиксированной длины и ширины, то есть лишнюю часть нужно скрыть, то есть добавить стиль:overflow: hidden, Итак, мы оборачиваем прокручиваемый контент так, чтобы его длина и ширина были равны родительскому dom, а затем вызываем стиль:overflow: hidden, элемент этой оболочки называетсяscrollPanel

Следующий:Мы знаем, что хотим быть такими же мощными, как нативные полосы прокрутки! Необходимо проектировать горизонтальные полосы прокрутки и вертикальные полосы прокрутки, полосы прокрутки иscrollPanelОн относится к отношениям между родственными узлами, потому что наличие полосы прокрутки не может привести к ошибке набора исходного стиля и поддерживаетtop,leftчтобы контролировать его положение, поэтому полоса прокруткиpositionдолжно бытьabsolute, Ну, мы называем горизонтальную полосу прокрутки как:hBar, вертикальная полоса прокрутки:vBar

наконец:мы разработалиscrollPanel,vBar,hBar, нам нужен родительdivДавайте завернем их и добавим стиль:position: relative

упражняться

Структура компонента дизайна

Прежде всего, наш плагин имеет в общей сложности 4 компонента, 3 из которых являются дочерними компонентами и 1 является родительским компонентом, а именно:vueScroll(родительский компонент),scrollPanel(обертывание дочерних компонентов, требующих прокрутки содержимого),vBar(вертикальная полоса прокрутки),hBar(горизонтальная полоса прокрутки)

Во-вторых, давайте спроектируем, за что отвечает каждый компонент. Компоненты здесь разделены на компоненты слоя управления и компоненты отображения (студенты, знакомые с реагированием, должны кое-что знать), а компоненты слоя отображения только выполняют функцию отображения:vBar,hBar,scrollPanel, Компонент уровня управления чем-то похож на процессор и может управлять различными состояниями подкомпонентов, такими как ширина, высота, цвет, прозрачность, положение и т. д. Компоненты уровня управления:vueScroll.

Реализация

hBar/vBar

hBar/vBarЭто горизонтальная полоса прокрутки и вертикальная полоса прокрутки, и реализуемые функции примерно одинаковы, поэтому они собраны вместе и описаны здесь.vBarНапример.

  1. propsПолучите свойства, переданные родительским компонентом, а именно:
 {
    height: vm.state.height + 'px',  //滚动条的高度
    width: vm.ops.width, // 滚动条的宽度
    position: 'absolute', 
    background: vm.ops.background, // 滚动条背景色
    top: vm.state.top + 'px', // 滚动条的高度
    transition: 'opacity .5s', // 消失/显示 所用的时间
    cursor: 'pointer', //
    opacity: vm.state.opacity, // 透明度
    userSelect: 'none' 
 }

2 события, в основном при движении мыши отображается полоса прокрутки.

...
render(_c){
    return _c(
       // ...
        {
            mouseenter: function(e) {
                vm.$emit('showVBar'); // 触发父组件事件,显示滚动条
            }
        }
       // ...
    )
}

вstateПредставляет состояние, которое можно изменить во время выполнения, иopsЭто параметр конфигурации, который передается пользователем.

scrollPanel

Для компонента, оборачивающего прокручиваемый контент, необходимо установить стиль:overflow: hidden.

  1. стиль
 var style = vm.scrollContentStyle;
 style.overflow = 'hidden';
 // ...
  {
      style: style
  }
 // ...
  1. мероприятие
 // ...
    render(_c) {
        // ...
            on: {
                mouseenter: function() {
                    vm.$emit('showBar');
                },
                mouseleave: function() {
                    vm.$emit('hideBar');
                }
            }
        // ...
    }
 // ...

vuescroll

компоненты управления. Управляйте состоянием, отображаемым дочерними компонентами, добавляйте различные события прослушивания и т. д.

  1. Получите элемент dom дочернего компонента, чтобы получить информацию о dom в реальном времени.
    // ...
     initEl() {
        this.scrollPanel.el = this.$refs['vueScrollPanel'] && this.$refs['vueScrollPanel'].$el;
        this.vScrollBar.el = this.$refs['vScrollBar'] && this.$refs['vScrollBar'].$el;
        this.hScrollBar.el = this.$refs['hScrollBar'] && this.$refs['hScrollBar'].$el;
    }
    // ...
  1. показать полосу прокрутки

Отображение полос прокрутки включает отображение горизонтальных полос прокрутки и отображение вертикальных полос прокрутки.Вот пример отображения вертикальных полос прокрутки:

    // ...
        var temp;
        var deltaY = {
            deltaY: this.vScrollBar.ops.deltaY // 获取用户配置的deltaY
        };
        if(!this.isMouseLeavePanel || this.vScrollBar.ops.keepShow){
            if ((this.vScrollBar.state.height = temp = this.getVBarHeight(deltaY))) { // 判断条件
                // 重新设置滚动条的状态
                this.vScrollBar.state.top = this.resizeVBarTop(temp);
                this.vScrollBar.state.height = temp.height;
                this.vScrollBar.state.opacity = this.vScrollBar.ops.opacity;
            }
        }
    // ...
  1. Получить высоту полосы прокрутки

Поскольку высота элемента dom не фиксирована, вам необходимо получить реальную высоту dom в режиме реального времени.Формула расчета высоты полосы прокрутки выглядит следующим образом:

var height = Math.max(
            scrollPanelHeight / 
            (scrollPanelScrollHeight / scrollPanelHeight), 
            this.vScrollBar.minBarHeight
            );

который:滚动条的高度:scrollPanel的高度 == scrollPanel的高度:dom元素高度

  1. resizeVBarTop, Во избежание ошибок и может найти высоту полосы прокрутки от родительского элемента.
 resizeVBarTop({height, scrollPanelHeight, scrollPanelScrollHeight, deltaY}) {
    // cacl the last height first
    var lastHeight = scrollPanelScrollHeight - scrollPanelHeight - this.scrollPanel.el.scrollTop;
    if(lastHeight < this.accuracy) {
        lastHeight = 0;
    }
    var time = Math.abs(Math.ceil(lastHeight / deltaY));
    var top = scrollPanelHeight - (height + (time * this.vScrollBar.innerDeltaY));
    return top;
}
  1. Слушайте события прокрутки колеса.
    // ...
    on: {
        wheel: vm.wheel
    }
    // ...
     wheel(e) {
        var vm = this;
        vm.showVBar();
        vm.scrollVBar(e.deltaY > 0 ? 1 : -1, 1);
        e.stopPropagation();
    }
    // ...
  1. Слушайте события перетаскивания полосы прокрутки
    listenVBarDrag: function() {
        var vm = this;
        var y;
        var _y;
        function move(e) {
            _y = e.pageY;
            var _delta = _y - y;
            vm.scrollVBar(_delta > 0 ? 1 : -1, Math.abs(_delta / vm.vScrollBar.innerDeltaY));
            y = _y;
        }
        function t(e) {
            var deltaY = {
                deltaY: vm.vScrollBar.ops.deltaY
            };
            if(!vm.getVBarHeight(deltaY)) {
                return;
            }
            vm.mousedown = true;
            y = e.pageY; // 记录初始的Y的位置
            vm.showVBar();
            document.addEventListener('mousemove', move);
            document.addEventListener('mouseup', function(e) {
                vm.mousedown = false;
                vm.hideVBar();
                document.removeEventListener('mousemove', move);
            });
        }
        this.listeners.push({
            dom: vm.vScrollBar.el,
            event: t,
            type: "mousedown"
        });
        vm.vScrollBar.el.addEventListener('mousedown', t); // 把事件放到数组里面,等销毁之前移除掉注册的时间。
    }
  1. Адаптируйтесь к мобильному терминалу, мониторьтеtouchмероприятие. Принцип подобен событию перетаскивания, за исключением того, что есть дополнительное суждение для определения, является ли текущее направление x или y.
    listenPanelTouch: function() {
        var vm = this;
        var pannel = this.scrollPanel.el;
        var x, y;
        var _x, _y;
        function move(e) {
            if(e.touches.length) {
                var touch = e.touches[0];
                _x = touch.pageX;
                _y = touch.pageY;
                var _delta = void 0;
                var _deltaX = _x - x;
                var _deltaY = _y - y;
                if(Math.abs(_deltaX) > Math.abs(_deltaY)) {
                    _delta = _deltaX;
                    vm.scrollHBar(_delta > 0 ? -1 : 1, Math.abs(_delta / vm.hScrollBar.innerDeltaX));
                } else if(Math.abs(_deltaX) < Math.abs(_deltaY)){
                    _delta = _deltaY;
                    vm.scrollVBar(_delta > 0 ? -1 : 1, Math.abs(_delta / vm.vScrollBar.innerDeltaY));
                }
                x = _x;
                y = _y;
            }
        }
        function t(e) {
            var deltaY = {
                deltaY: vm.vScrollBar.ops.deltaY
            };
            var deltaX = {
                deltaX: vm.hScrollBar.ops.deltaX
            };
            if(!vm.getHBarWidth(deltaX) && !vm.getVBarHeight(deltaY)) {
                return;
            }
            if(e.touches.length) {
                e.stopPropagation();
                var touch = e.touches[0];
                vm.mousedown = true;
                x = touch.pageX;
                y = touch.pageY;
                vm.showBar();
                pannel.addEventListener('touchmove', move);
                pannel.addEventListener('touchend', function(e) {
                    vm.mousedown = false;
                    vm.hideBar();
                    pannel.removeEventListener('touchmove', move);
                });
            }
        }
        pannel.addEventListener('touchstart', t);
        this.listeners.push({
            dom: pannel,
            event: t,
            type: "touchstart"
        });
    }
  1. прокрутить содержимое

Принцип прокрутки контента не что иное, как изменениеscrollPanelизscrollTop/scrollLeftДля достижения цели управления контентом для перемещения вверх и вниз, влево и вправо.

    scrollVBar: function(pos, time) {
        // >0 scroll to down  <0 scroll to up
         
        var top = this.vScrollBar.state.top; 
        var scrollPanelHeight = getComputed(this.scrollPanel.el, 'height').replace('px', "");
        var scrollPanelScrollHeight = this.scrollPanel.el.scrollHeight;
        var scrollPanelScrollTop = this.scrollPanel.el.scrollTop;
        var height = this.vScrollBar.state.height;
        var innerdeltaY = this.vScrollBar.innerDeltaY;
        var deltaY = this.vScrollBar.ops.deltaY;
        if (!((pos < 0 && top <= 0) || (scrollPanelHeight <= top + height && pos > 0) || (Math.abs(scrollPanelScrollHeight - scrollPanelHeight) < this.accuracy))) {
            var Top = top + pos * innerdeltaY * time;
            var ScrollTop = scrollPanelScrollTop + pos * deltaY * time;
            if (pos < 0) {
                // scroll ip
                this.vScrollBar.state.top = Math.max(0, Top);
                this.scrollPanel.el.scrollTop = Math.max(0, ScrollTop);
            } else if (pos > 0) {
                // scroll down
                this.vScrollBar.state.top = Math.min(scrollPanelHeight - height, Top);
                this.scrollPanel.el.scrollTop = Math.min(scrollPanelScrollHeight - scrollPanelHeight, ScrollTop);
            }
        }
        // 这些是传递给父组件的监听滚动的函数的。
        var content = {};
        var bar = {};
        var process = "";
        content.residual = (scrollPanelScrollHeight - scrollPanelScrollTop - scrollPanelHeight);
        content.scrolled = scrollPanelScrollTop;
        bar.scrolled = this.vScrollBar.state.top;
        bar.residual = (scrollPanelHeight - this.vScrollBar.state.top - this.vScrollBar.state.height);
        bar.height = this.vScrollBar.state.height;
        process = bar.scrolled/(scrollPanelHeight - bar.height);
        bar.name = "vBar";
        content.name = "content";
        this.$emit('vscroll', bar, content, process);
    },
  1. Уничтожить зарегистрированные события.

Мы только что поместили зарегистрированные события в массив listeners, мы можем уничтожить их в хуке beforedestroy.

    // remove the registryed event.
    this.listeners.forEach(function(item) {
        item.dom.removeEventListener(item.event, item.type);
    });

Приведенная выше часть является основным исходным кодом этого компонента.

запустить скриншот

Скриншот операции на стороне ПК показан на следующем рисунке:

После регистрации события прослушивателя оно показано на следующем рисунке:

Запустите скриншоты на своем телефоне:

Видно, что производительность родной полосы прокрутки такая же.

Заключение и понимание

Вышеупомянутое в основном завершило дизайн полосы прокрутки, которую я разработал.Во-первых, я очень благодарен Nuggets за предоставление мне такой платформы для обмена, а затем я благодарю автора slimScroll за то, что он подал мне такую ​​идею. После завершения этого плагина я знаю больше о элементах scrollWidth, scrollHeigh, scrollTop, scrollLeft of dom и, наконец, прикрепляюгитхаб проектАдрес, если эта статья была вам полезна, нажмите звездочку, большое спасибо~