效果如下
创建scrollmove.js,内容如下
//获取原生dom对象
export function getDom(name) {
return document.querySelector(name)
}
//移动元素
// mouseDrag(e,()=>{},()=>{}) //mousedown事件e,move方法,up方法
export function mouseDrag(d, move, up) {
let dx = d.clientX, dy = d.clientY
const moveFn = (m) => {
if (dx == m.clientX && dy == m.clientY) {
return
}
move(m)
}
const upFn = (u) => {
window.removeEventListener("mousemove", moveFn)
window.removeEventListener("mouseup", upFn)
if (up) up(u)
}
window.addEventListener("mousemove", moveFn)
window.addEventListener("mouseup", upFn)
}
// 在mousedonw中使用,返回移动偏移
// 滚轮父盒子 边界距离 轴向 每次返回最大移动距离
// node origin noX noY maxSize
export class BindScrollMove {
constructor(options, cb) {
//默认配置
const e = options.e
this._maxSize = 12
this._origin = 20
this._interval = 16
this.cb = cb
//滚轮父盒子 , 边界距离,轴向,最大移动距离
let { node, origin, noX, noY, maxSize, interval } = options
if (typeof node === 'string') {
node = getDom(node)
}
if (maxSize) this._maxSize = maxSize
if (interval) this._interval = interval
if (origin !== undefined) this._origin = origin
this._noX = noX
this._noY = noY
const clientRect = node.getBoundingClientRect();
//记录存在区域
this._startX = clientRect.left
this._startY = clientRect.top
this._endX = this._startX + node.clientWidth;
this._endY = this._startY + node.clientHeight;
//记录按下
this._downX = e.clientX;
this._downY = e.clientY;
mouseDrag(e, this.mouseMove.bind(this), this.mouseUp.bind(this))
}
mouseMove(e) {
if ((Math.abs(e.clientX - this._downX) > 15 && !this._noX) ||
(Math.abs(e.clientY - this._downY) > 15 && !this._noY)) {
this.mouseMoveFn(e)
this._downX = e.clientX;
this._downY = e.clientY;
}
}
mouseMoveFn(e) {
const x = e.clientX
const y = e.clientY
const bL = x - this._startX
const bR = this._endX - x
const bT = y - this._startY
const bB = this._endY - y
//编辑开始
if (!this._noX && !this._noY) {
//x,y轴都有监听
if (bL < this._origin || bR < this._origin || bB < this._origin || bT < this._origin) {
this.addInterVal({ bL, bR, bT, bB })
} else {
this.removeInterVal()
}
} else if (!this._noX && this._noY) {
//仅有x轴监听
if (bL < this._origin || bR < this._origin) {
this.addInterVal({ bL, bR, bT, bB })
} else {
this.removeInterVal()
}
} else if (this._noX && !this._noY) {
//仅有y轴监听
if (bB < this._origin || bT < this._origin) {
this.addInterVal({ bL, bR, bT, bB })
} else {
this.removeInterVal()
}
}
}
addInterVal(ops) {
const { bL, bR, bT, bB } = ops
//添加移动事件
clearInterval(this._moveInterval)
this._moveInterval = setInterval(() => {
let dx = 0, dy = 0;
//移动方向和距离
if (bL < this._origin) {
dx = (bL - this._origin) / 5;
} else if (bR < this._origin) {
dx = -(bR - this._origin) / 5;
}
if (bT < this._origin) {
dy = (bT - this._origin) / 5;
} else if (bB < this._origin) {
dy = -(bB - this._origin) / 5;
}
dx = Math.round(dx)
dy = Math.round(dy)
dx = Math.min(this._maxSize, dx)
dy = Math.min(this._maxSize, dy)
dx = Math.max(-this._maxSize, dx)
dy = Math.max(-this._maxSize, dy)
if (this.callback) {
this.callback({ dx, dy }) //将事件发出去
}
if (this.cb) {
this.cb({ dx, dy })
}
}, this._interval)
}
removeInterVal() {
clearInterval(this._moveInterval)
this._moveInterval = null
}
mouseUp(e) {
clearInterval(this._moveInterval)
this._moveInterval = null
}
}
vue页面,内容如下
<template>
<div>
<div class="pnode">
<div class="snode">
<div class="bg" v-for="num in 600" :key="num">{{ num }}</div>
<div
id="slider"
@mousedown="sliderDown"
:style="{ left: left + 'px', top: top + 'px' }"
></div>
</div>
</div>
</div>
</template>
<script>
import { BindScrollMove, mouseDrag } from "@/utils/scrollmove";
export default {
data() {
return {
left: 0,
top: 0,
};
},
methods: {
sliderDown(e) {
const L = e.clientX;
const T = e.clientY;
const defaultL = this.left;
const defaultT = this.top;
let scrollOffsetX = 0; //记录偏移量
let scrollOffsetY = 0; //记录偏移量
let slidL, slidT; //记录移动量
const pNode = document.querySelector(".pnode");
const sNode = document.querySelector(".snode");
const slider = document.querySelector("#slider");
const maxL = sNode.clientWidth - slider.clientWidth;
const maxT = sNode.clientHeight - slider.clientHeight;
// 正常移动滑块事件
mouseDrag(e, (m) => {
const X = m.clientX;
const Y = m.clientY;
slidL = X - L + defaultL; //滑块的left
slidT = Y - T + defaultT; //滑块的top
calcPosition();
});
// 移动至边界事件
const moveC = new BindScrollMove({
e, //按下事件e,必填
node: ".pnode", //滚动条父盒子,必填
origin: 30, //滑块距离边界多少开始计算
// noX: true, //不计算X方向
// noY: true, //不计算Y方向
// interval:10, //10毫秒一次
maxSize: 15, //callback最大移动距离
});
moveC.callback = (move) => {
// console.log(move); //{dx,dy}
if (this.left > 0 && this.left < maxL) {
scrollOffsetX += move.dx;
}
if (this.top > 0 && this.top < maxT) {
scrollOffsetY += move.dy;
}
if (move.dy === 0 && move.dx === 0) return;
pNode.scrollTop += move.dy; //滚动条调用
pNode.scrollLeft += move.dx;
calcPosition();
};
//计算移动和偏移和叠加的后的位置
const calcPosition = () => {
let zL = slidL + scrollOffsetX;
let zT = slidT + scrollOffsetY;
if (zL < 0) zL = 0;
if (zT < 0) zT = 0;
if (zL > maxL) zL = maxL;
if (zT > maxT) zT = maxT;
this.left = zL;
this.top = zT;
};
},
},
};
</script>
<style lang="less" scoped>
.pnode {
margin: 200px auto;
width: 400px;
height: 400px;
max-height: 500px;
border: 1px solid red;
overflow: auto;
.snode {
min-width: 2000px;
min-height: 2000px;
text-align: left;
position: relative;
.bg {
display: inline-block;
font-size: 30px;
color: #999;
margin: 20px;
user-select: none;
}
#slider {
position: absolute;
top: 0;
left: 0;
background: green;
opacity: 0.8;
width: 50px;
height: 50px;
user-select: none;
}
}
}
</style>
说明
BindScrollMove方法仅根据用户移动事件得到滚动偏移量,最终需要通过calcPosion综合计算