230 lines
5.8 KiB
JavaScript
230 lines
5.8 KiB
JavaScript
/**
|
|
* {滚动条组件}
|
|
* @author chensbox <chensbox@foxmail.com>
|
|
* @date 2023/03/20 15:17:25
|
|
*/
|
|
|
|
import { css, bind, html, unbind, Component } from '@bd/core'
|
|
|
|
class Scroll extends Component {
|
|
static props = {
|
|
axis: {
|
|
type: String,
|
|
default: 'xy',
|
|
observer(val) {
|
|
this.onAxisChange(val)
|
|
}
|
|
},
|
|
distance: 0
|
|
}
|
|
dragging = false
|
|
hovering = false
|
|
static styles = [
|
|
css`
|
|
:host {
|
|
position: relative;
|
|
overflow: hidden;
|
|
display: block;
|
|
&::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
scrollbar-width: none; //火狐专属
|
|
}
|
|
.scroll {
|
|
position: relative;
|
|
}
|
|
.scroll-bar {
|
|
display: none;
|
|
position: absolute;
|
|
background: #909399;
|
|
width: 0;
|
|
height: 0;
|
|
opacity: 0;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
transition: opacity ease-in-out 0.2s;
|
|
|
|
&.vertical {
|
|
width: 8px;
|
|
top: 0;
|
|
right: 0;
|
|
}
|
|
&.horizon {
|
|
height: 8px;
|
|
bottom: 0;
|
|
left: 0;
|
|
}
|
|
&:hover {
|
|
opacity: 0.5 !important;
|
|
}
|
|
}
|
|
:host([disabled]) {
|
|
overflow: hidden !important;
|
|
.scroll-bar {
|
|
visibility: hidden;
|
|
}
|
|
}
|
|
`
|
|
]
|
|
onAxisChange(val) {
|
|
this.style.overflow = 'hidden'
|
|
if (val === 'xy') {
|
|
this.style.overflow = 'auto'
|
|
} else {
|
|
this.style[`overflow-${val}`] = 'auto'
|
|
}
|
|
}
|
|
|
|
render() {
|
|
return html`
|
|
<div ref="scrollView" class="scroll">
|
|
<slot></slot>
|
|
</div>
|
|
<div ref="vertical" class="scroll-bar vertical"></div>
|
|
<div ref="horizon" class="scroll-bar horizon"></div>
|
|
`
|
|
}
|
|
|
|
onmouseenter() {
|
|
this.hovering = true
|
|
this.$refs.vertical.style.opacity = 0.3
|
|
this.$refs.horizon.style.opacity = 0.3
|
|
}
|
|
onmouseleave() {
|
|
this.hovering = false
|
|
if (!this.dragging) {
|
|
this.$refs.vertical.style.opacity = 0
|
|
this.$refs.horizon.style.opacity = 0
|
|
}
|
|
}
|
|
onmousedown(e) {
|
|
this.dragging = true
|
|
const {
|
|
clientHeight,
|
|
scrollHeight,
|
|
clientWidth,
|
|
scrollWidth,
|
|
verticalHeight,
|
|
horizonWidth
|
|
} = this
|
|
let start, cur
|
|
const isVerticalScroll = e.target === this.$refs.vertical
|
|
if (isVerticalScroll) {
|
|
start = e.clientY
|
|
cur = this.scrollTop
|
|
} else {
|
|
start = e.clientX
|
|
cur = this.scrollLeft
|
|
}
|
|
|
|
const onmousemove = bind(document, 'mousemove', e => {
|
|
let dif, rang, progress
|
|
if (isVerticalScroll) {
|
|
dif = e.clientY - start
|
|
rang = clientHeight - verticalHeight
|
|
progress = dif / rang
|
|
this.scrollTop = cur + progress * (scrollHeight - clientHeight)
|
|
} else {
|
|
dif = e.clientX - start
|
|
rang = clientWidth - horizonWidth
|
|
progress = dif / rang
|
|
this.scrollLeft = cur + progress * (scrollWidth - clientWidth)
|
|
}
|
|
})
|
|
|
|
this.onmouseup = bind(document, 'mouseup', () => {
|
|
this.dragging = false
|
|
if (!this.hovering) {
|
|
this.$refs.vertical.style.opacity = 0
|
|
this.$refs.horizon.style.opacity = 0
|
|
}
|
|
unbind(document, 'mousemove', onmousemove)
|
|
})
|
|
}
|
|
onscroll(ev) {
|
|
const {
|
|
distance,
|
|
scrollTop,
|
|
scrollLeft,
|
|
clientHeight,
|
|
scrollHeight,
|
|
clientWidth,
|
|
scrollWidth,
|
|
verticalHeight,
|
|
horizonWidth
|
|
} = this
|
|
const { vertical, horizon } = this.$refs
|
|
const verticalProgress = scrollTop / (scrollHeight - clientHeight) || 0
|
|
const horizonProgress = scrollLeft / (scrollWidth - clientWidth) || 0
|
|
vertical.style.top = `${
|
|
scrollTop + (clientHeight - verticalHeight) * verticalProgress
|
|
}px`
|
|
vertical.style.left = scrollLeft + clientWidth - 8 + 'px'
|
|
horizon.style.left = `${
|
|
scrollLeft + (clientWidth - horizonWidth) * horizonProgress
|
|
}px`
|
|
horizon.style.top = scrollTop + clientHeight - 8 + 'px'
|
|
|
|
if (!ev) {
|
|
//事件对象不存在,则是手动调用的 不触发事件。
|
|
return
|
|
}
|
|
if (this.verticalHeight) {
|
|
if (scrollHeight - scrollTop - clientHeight <= distance) {
|
|
this.$emit('reach-bottom')
|
|
}
|
|
if (scrollTop === 0) {
|
|
this.$emit('reach-top')
|
|
}
|
|
}
|
|
}
|
|
initScrollBar() {
|
|
const { axis, clientHeight, scrollHeight, clientWidth, scrollWidth } = this
|
|
const { vertical, horizon } = this.$refs
|
|
// console.log(this.$refs)
|
|
if (clientHeight !== scrollHeight) {
|
|
this.verticalHeight = (clientHeight / scrollHeight) * clientHeight
|
|
} else {
|
|
this.verticalHeight = 0
|
|
}
|
|
if (clientWidth !== scrollWidth) {
|
|
this.horizonWidth = (clientWidth / scrollWidth) * clientWidth
|
|
} else {
|
|
this.horizonWidth = 0
|
|
}
|
|
if (axis.includes('x')) {
|
|
horizon.style.display = 'block'
|
|
horizon.style.width = this.horizonWidth + 'px'
|
|
bind(horizon, 'mousedown', this.onmousedown.bind(this))
|
|
}
|
|
if (axis.includes('y')) {
|
|
vertical.style.display = 'block'
|
|
vertical.style.height = this.verticalHeight + 'px'
|
|
bind(vertical, 'mousedown', this.onmousedown.bind(this))
|
|
}
|
|
}
|
|
mounted() {
|
|
this.initScrollBar()
|
|
|
|
this.$on('mouseenter', this.onmouseenter)
|
|
this.$on('mouseleave', this.onmouseleave)
|
|
this.$on('scroll', this.onscroll)
|
|
this.resizeObserver = new ResizeObserver(() => {
|
|
this.initScrollBar()
|
|
this.onscroll()
|
|
})
|
|
this.resizeObserver.observe(this.$refs.scrollView)
|
|
}
|
|
unmounted() {
|
|
this.resizeObserver.disconnect()
|
|
unbind(document, 'mouseup', this.onmouseup)
|
|
this.$of('mouseenter', this.onmouseenter)
|
|
this.$of('mouseleave', this.onmouseleave)
|
|
this.$of('scroll', this.onscroll)
|
|
unbind(this.$refs.vertical, 'mousedown', this.onmousedown)
|
|
unbind(this.$refs.horizon, 'mousedown', this.onmousedown)
|
|
}
|
|
}
|
|
|
|
customElements.define('wc-scroll', Scroll)
|
JavaScript
98.9%
CSS
1.1%