需求分析
- 可模拟原生滚动条。
- 在滚动区域滚动滚轮内容可进行滚动。
- 鼠标点击滚动条拖拽可滚动内容。
- 内容区域滚动条关系为等比关系,内容滑动多少滚动条就滚动多少。
方法实现
一、思路:
1、先在scroll组件容器外层固定高宽并设置overflow:hidden;,这样在容器内的内容就不会跑出容器外,这是做滚动组件的基础。
2、监听wheel鼠标滚轮事件,判断当鼠标在容器中进行滚动时触发滚动事件,并对Y轴进行translateY位移(性能好)。判断最顶端与最低端的值,超过这个值则停止滚动抵达边界。
3、用css写一个模拟滚动条,模仿原生滚动条特性,监听鼠标点击事件、鼠标移动事件、鼠标松开事件,当点击滚动条时根据鼠标移动事件的位移进行内容区Y轴的移动,并滚动条等比例移动,送开鼠标则停止移动。
4、内容区与容器位置和滚动条与滚动条区域为等比关系,两者间保持联动。
二、方法实现
1、datePicker外部组件传参定义:
<template>
<div class="box">
<t-scroll style="width: 300px; height: 400px;">
<p id="test2">1111122222222222333</p>
<p>2</p>
<p>3</p>
<p>4</p>
<p>5</p>
<p>6</p>
<p>7</p>
<p>8</p>
<p>9</p>
<p>10</p>
</t-scroll>
</div>
</template>
2、datePicker内部组件实现
html
<template>
<div ref="parent" class="t-scroll-content"
@mouseenter="onMouseEnter"
@mousemove="onMouseMove"
@mouseleave="onMouseLeave"
@wheel="onWheel">
<div ref="child" class="t-scroll-content-box" :style="{transform: `translateY(${this.contentY}px)`}">
<slot></slot>
</div>
<div class="t-scroll-content-track" v-show="scrollBarVisible">
<div class="t-scroll-content-bar" ref="bar" @mousedown="onMouseDownScrollBar"
@selectstart="onSelectStartScrollBar">
<div class="t-scroll-content-inner"></div>
</div>
</div>
</div>
</template>
js
<script>
export default {
name: 'TScroll',
data() {
return {
scrollBarVisible: false,
isScrolling: false,
startPosition: undefined,
endPosition: undefined,
scrollBarY: 0,
contentY: 0,
barHeight: undefined,
parentHeight: undefined,
mouseIn: false
}
},
computed: {
maxScrollHeight() {
return this.parentHeight - this.barHeight
},
childHeight() {
return this.$refs.child.getBoundingClientRect().height
}
},
mounted() {
this.listenToDocument()
this.parentHeight = this.$refs.parent.getBoundingClientRect().height
this.updateScrollBar()
},
methods: {
listenToDocument() {
document.addEventListener('mousemove', e => this.onMouseMoveScrollBar(e))
document.addEventListener('mouseup', e => this.onMouseUpScrollBar())
},
calculateContentYFromDeltaY(deltaY) {
let contentY = this.contentY
if (deltaY > 20) {
contentY -= 20 * 3
} else if (deltaY < -20) {
contentY -= -20 * 3
} else {
contentY -= deltaY * 3
}
return contentY
},
onWheel(e) {
this.updateContentY(e.deltaY, () => e.preventDefault())
this.updateScrollBar()
},
updateContentY(deltaY, fn) {
let maxHeight = this.calculateContentYMax()
this.contentY = this.calculateContentYFromDeltaY(deltaY)
if (this.contentY > 0) {
this.contentY = 0
} else if (this.contentY < -maxHeight) {
this.contentY = -maxHeight
} else {
fn && fn()
}
},
calculateContentYMax() {
let {borderTopWidth, borderBottomWidth, paddingTop, paddingBottom} = window.getComputedStyle(this.$refs.parent)
borderTopWidth = parseInt(borderTopWidth)
borderBottomWidth = parseInt(borderBottomWidth)
paddingTop = parseInt(paddingTop)
paddingBottom = parseInt(paddingBottom)
return this.childHeight - this.parentHeight + (borderTopWidth + borderTopWidth + paddingTop + paddingBottom)
},
updateScrollBar() {
let parentHeight = this.parentHeight
let childHeight = this.childHeight
this.barHeight = parentHeight * parentHeight / childHeight
this.$refs.bar.style.height = this.barHeight + 'px'
this.scrollBarY = -parentHeight * this.contentY / childHeight
this.$refs.bar.style.transform = `translateY(${this.scrollBarY}px)`
},
onMouseEnter() {
this.scrollBarVisible = true
this.mouseIn = true
},
onMouseLeave() {
this.mouseIn = false
if (!this.isScrolling) {
this.scrollBarVisible = false
}
},
onMouseMove() {
this.mouseIn = true
},
onMouseDownScrollBar(e) {
this.isScrolling = true
let {screenX, screenY} = e
this.startPosition = {x: screenX, y: screenY}
},
onMouseMoveScrollBar(e) {
if (!this.isScrolling) {return}
this.endPosition = {x: e.screenX, y: e.screenY}
let delta = {x: this.endPosition.x - this.startPosition.x, y: this.endPosition.y - this.startPosition.y}
this.scrollBarY = this.calculateScrollBarY(delta)
this.contentY = -(this.childHeight * this.scrollBarY / this.parentHeight)
this.startPosition = this.endPosition
this.$refs.bar.style.transform = `translate(0px, ${this.scrollBarY}px)`
},
calculateScrollBarY(delta) {
let newValue = parseInt(this.scrollBarY) + delta.y
if (newValue < 0) {
newValue = 0
} else if (newValue > this.maxScrollHeight) {
newValue = this.maxScrollHeight
}
return newValue
},
onMouseUpScrollBar(e) {
this.isScrolling = false
if (!this.mouseIn) {
this.scrollBarVisible = false
}
},
onSelectStartScrollBar(e) {
e.preventDefault()
}
}
}
</script>
完整代码可参考我的GitHub:https://github.com/A-Tione/tione/blob/master/src/scroll/scroll.vue