比BackTop更强的快速定位组件
效果
有一个快捷浏览
的滑块组件,拖动它可以滚动整个页面。
不管height
多大的页面,都可以通过移动滑块来到达页面的任何位置。
实现拖拽容器组件
export default {
props: {
containTrigger: Boolean,
lockAxis: {
type: String,
validator(v) {
return ['x', 'y'].includes(v);
},
},
restrictedArea: Boolean,
},
data() {
return {
dragging: false,
beginPos: { x: 0, y: 0 }, // 鼠标按下位置
translate: { x: 0, y: 0 },
position: { left: 0, top: 0 },
};
},
computed: {
style() {
const { translate, position } = this;
const style = {
position: this.restrictedArea ? 'absolute' : 'fixed',
left: `${position.left}px`,
top: `${position.top}px`,
};
if (this.dragging) {
Object.assign(style, {
// 提升层级,防止被遮挡
zIndex: '9999',
// 防止拖拽变文字选择
userSelector: 'none',
transform: `translate(${translate.x}px, ${translate.y}px)`,
});
}
return style;
},
},
render(h) {
return h('div', { style: this.style }, this.$slots.default);
},
mounted() {
this.init();
},
methods: {
setProgress(percent) {
const { width: w } = this.restrictedEl.getBoundingClientRect();
const { width: w2 } = this.$el.getBoundingClientRect();
this.position.left = (w - w2) * percent;
},
init() {
// 拖拽元素 默认使用组件的第一个元素作为拖拽元素
const triggerEl = this.containTrigger
? this.$el.querySelector('[drag-trigger]')
: this.$slots.default[0].elm;
// 拖拽限制的祖先元素(拖拽范围)
let restrictedEl;
if (this.restrictedArea) {
restrictedEl = this.$el.parentNode;
while (
getComputedStyle(restrictedEl).position === 'static' &&
restrictedEl !== document.documentElement
) {
restrictedEl = restrictedEl.parentNode;
}
} else {
restrictedEl = document.documentElement;
}
this.restrictedEl = restrictedEl;
triggerEl.addEventListener('mousedown', this.handleMouseDown, true);
},
handleMouseDown(e) {
e.stopPropagation();
this.dragging = true;
this.beginPos = { x: e.pageX, y: e.pageY };
this.restrictedElRect = this.restrictedEl.getBoundingClientRect();
this.targetRect = e.target.getBoundingClientRect();
document.addEventListener('mousemove', this.handleMouseMove, true);
document.addEventListener('mouseup', this.handleMouseUp, true);
},
handleMouseMove(e) {
e.stopPropagation();
requestAnimationFrame(() => {
this.translate = this.getChangePosition(e);
});
},
handleMouseUp(e) {
e.stopPropagation();
this.dragging = false;
document.removeEventListener('mousemove', this.handleMouseMove, true);
document.removeEventListener('mouseup', this.handleMouseUp, true);
requestAnimationFrame(() => {
let { left, top } = this.position;
const { x, y } = this.getChangePosition(e);
left += x;
top += y;
this.position = { left, top };
});
},
getChangePosition(e) {
let { pageX: x, pageY: y } = e;
const {
left: l,
right: r,
top: t,
bottom: b,
width: w,
} = this.restrictedElRect;
const {
left: l2,
right: r2,
top: t2,
bottom: b2,
width: w2,
} = this.targetRect;
const { x: x2, y: y2 } = this.beginPos;
x =
this.lockAxis === 'x'
? 0
: x < l - l2 + x2
? l - l2
: x > r - r2 + x2
? r - r2
: x - x2;
y =
this.lockAxis === 'y'
? 0
: y < t - t2 + y2
? t - t2
: y > b b2 + y2
? b - b2
: y - y2;
this.$emit('changePosition', (this.position.left + x) / (w - w2));
return { x, y };
},
},
};
props说明
-
containTrigger
布尔值。告诉触发拖拽的部分是否在组件内部。若在组件内部则需要在拖拽元素上添加drag-trigger
属性。 -
lockAxis
枚举值。x|y
,可以锁定一个轴向不能拖拽。 -
restrictedArea
布尔值。表示是否限制了拖拽区域。默认未限制,拖拽区域为document
;若限制,则往上查找第一个position
非static
的祖先元素。
data说明
dragging
表示当前是否正在拖拽中。从鼠标按下开始,到鼠标抬起结束。beginPos
鼠标按下的位置。translate
鼠标移动位置与鼠标按下位置的偏移值,使用transform:translate(x,y)
实现偏移。position
鼠标抬起时,将偏移量传给定位的left
和top
值。
快捷导航组件
// 文章页快捷进度条
const QuickProgressBar = {
render(h) {
return h(
'div',
{
staticClass: 'quick-progressbar-container',
},
[
h(
DragWrapper,
{
ref: 'dragger',
props: { restrictedArea: true, lockAxis: 'y' },
on: {
changePosition: persent => {
requestAnimationFrame(() => {
document.documentElement.scrollTop =
document.documentElement.scrollHeight * persent;
});
},
},
},
[h('span', { staticClass: 'quick-progressbar-slider' }, '快捷浏览')]
),
]
);
},
mounted() {
this.$watch(
'$refs.dragger.dragging',
v => {
if (v) {
document.removeEventListener('scroll', this.handleScroll);
} else {
document.addEventListener('scroll', this.handleScroll);
}
},
{ immediate: true }
);
},
methods: {
handleScroll() {
this.$refs.dragger.setProgress(
document.documentElement.scrollTop /
document.documentElement.scrollHeight
);
},
},
};
难点
当快捷浏览组件拖动时,需要滚动页面;页面滚动时,又会移动快捷浏览组件。如何避免相互调用?
- 外部组件会监听
DragWrapper
组件是否处于拖动状态。 - 若开始拖拽,则先解绑对页面滚动事件的监听;结束拖拽时,绑定对页面滚动的监听。
DragWrapper
组件拖拽发生时,通过$emit('changePosition', percent)
来通知页面需要滚动和高度百分比。- 鼠标滚动页面时,
DragWrapper
组件的拖拽肯定是没有进行的,此时会绑定页面滚动事件的监听,在监听的处理器中调用DragWrapper
的setProgress(percent)
来改变拖动元素的位置。
转载自:https://juejin.cn/post/7283769640803123256