предисловие
Я помню, как однажды случайно просматривал проект 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Например.
-
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.
- стиль
var style = vm.scrollContentStyle;
style.overflow = 'hidden';
// ...
{
style: style
}
// ...
- мероприятие
// ...
render(_c) {
// ...
on: {
mouseenter: function() {
vm.$emit('showBar');
},
mouseleave: function() {
vm.$emit('hideBar');
}
}
// ...
}
// ...
vuescroll
компоненты управления. Управляйте состоянием, отображаемым дочерними компонентами, добавляйте различные события прослушивания и т. д.
- Получите элемент 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;
}
// ...
- показать полосу прокрутки
Отображение полос прокрутки включает отображение горизонтальных полос прокрутки и отображение вертикальных полос прокрутки.Вот пример отображения вертикальных полос прокрутки:
// ...
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;
}
}
// ...
- Получить высоту полосы прокрутки
Поскольку высота элемента dom не фиксирована, вам необходимо получить реальную высоту dom в режиме реального времени.Формула расчета высоты полосы прокрутки выглядит следующим образом:
var height = Math.max(
scrollPanelHeight /
(scrollPanelScrollHeight / scrollPanelHeight),
this.vScrollBar.minBarHeight
);
который:滚动条的高度:scrollPanel的高度 == scrollPanel的高度:dom元素高度
-
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;
}
- Слушайте события прокрутки колеса.
// ...
on: {
wheel: vm.wheel
}
// ...
wheel(e) {
var vm = this;
vm.showVBar();
vm.scrollVBar(e.deltaY > 0 ? 1 : -1, 1);
e.stopPropagation();
}
// ...
- Слушайте события перетаскивания полосы прокрутки
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); // 把事件放到数组里面,等销毁之前移除掉注册的时间。
}
- Адаптируйтесь к мобильному терминалу, мониторьте
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"
});
}
- прокрутить содержимое
Принцип прокрутки контента не что иное, как изменение
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);
},
- Уничтожить зарегистрированные события.
Мы только что поместили зарегистрированные события в массив 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 и, наконец, прикрепляюгитхаб проектАдрес, если эта статья была вам полезна, нажмите звездочку, большое спасибо~