This repository has been archived on 2023-08-30. You can view files and clone it, but cannot push or open issues/pull-requests.
bytedo
/
wcui
Archived
1
0
Fork 0
wcui/src/scroll/index.wc

468 lines
11 KiB
Plaintext

<template>
<div class="container">
<div class="wrapper">
<div class="content"><slot /></div>
</div>
</div>
<div class="is-horizontal"><span class="thumb"></span></div>
<div class="is-vertical"><span class="thumb"></span></div>
</template>
<style lang="scss">
:host {
position: relative;
display: block;
width: 100%;
height: 100%;
.container {
overflow: hidden;
position: relative;
width: 100%;
height: 100%;
}
.wrapper {
overflow: auto;
width: 100%;
height: 100%;
}
}
/* 横向 */
.is-horizontal,
.is-vertical {
display: none;
visibility: hidden;
position: absolute;
display: flex;
justify-content: flex-end;
z-index: 10240;
opacity: 0;
user-select: none;
transition: opacity 0.3s linear, visibility 0.3s linear;
.thumb {
display: block;
border-radius: 5px;
background: rgba(44, 47, 53, 0.25);
cursor: default;
transition: width 0.1s linear, height 0.1s linear;
&:hover {
background: rgba(44, 47, 53, 0.5);
}
}
}
.is-horizontal {
flex-direction: column;
left: 0;
bottom: 0;
width: 100%;
height: 10px;
.thumb {
width: 0;
height: 6px;
&:hover {
height: 10px;
}
}
}
/* 纵向 */
.is-vertical {
top: 0;
right: 0;
width: 10px;
height: 100%;
.thumb {
width: 6px;
height: 0;
&:hover {
width: 10px;
}
}
}
:host(:hover) {
.is-horizontal,
.is-vertical {
visibility: visible;
opacity: 1;
}
}
:host([axis='x']) {
.wrapper {
overflow-y: hidden;
}
.is-vertical {
display: none;
}
}
:host([axis='y']) {
.wrapper {
overflow-x: hidden;
}
.is-horizontal {
display: none;
}
}
:host([disabled]) {
.wrapper {
overflow: hidden;
}
.is-vertical,
.is-horizontal {
display: none;
}
}
</style>
<script>
import $ from '../utils'
const AXIS_LIST = ['x', 'y', 'xy']
/* */
export default class Scroll {
props = {
axis: 'xy', // 滚动方向, 默认x轴和y轴都可以滚动
delay: 1000, // 节流防抖延迟
distance: 1 // 触发距离阀值, 单位像素
}
state = {
width: 0, // 滚动组件的真实宽度(可视宽高)
height: 0, // 滚动组件的真实高度(可视宽高)
widthFixed: 0, // 滚动组件的宽度修正值
heightFixed: 0, // 滚动组件的高度修正值
scrollWidth: 0, // 滚动组件的滚动宽度
scrollHeight: 0, // 滚动组件的滚动高度
xBar: 0, // 横轴长度
yBar: 0, // 纵轴长度
thumbX: 0, //横向条滚动距离
thumbY: 0 // 纵向条滚动距离
}
__init__() {
/* render */
this.__WRAPPER__ = this.root.children[1].children[0]
this.__CONTENT__ = this.__WRAPPER__.firstElementChild
this.__X__ = this.root.children[2].children[0]
this.__Y__ = this.root.children[3].children[0]
this.__last__ = 0
}
get scrollTop() {
return this.__WRAPPER__.scrollTop
}
set scrollTop(n) {
n = +n
if (n === n) {
this.__WRAPPER__.scrollTop = n
}
}
get scrollLeft() {
return this.__WRAPPER__.scrollLeft
}
set scrollLeft(n) {
n = +n
if (n === n) {
this.__WRAPPER__.scrollLeft = n
}
}
get scrollHeight() {
return this.__WRAPPER__.scrollHeight
}
get scrollWidth() {
return this.__WRAPPER__.scrollWidth
}
_fetchScrollX(moveX) {
var { scrollWidth, width, xBar } = this.state
if (moveX < 0) {
moveX = 0
} else if (moveX > width - xBar) {
moveX = width - xBar
}
this.__WRAPPER__.scrollLeft = (scrollWidth - width) * (moveX / (width - xBar))
this.__X__.style.transform = `translateX(${moveX}px)`
return moveX
}
_fetchScrollY(moveY) {
var { scrollHeight, height, yBar } = this.state
if (moveY < 0) {
moveY = 0
} else if (moveY > height - yBar) {
moveY = height - yBar
}
this.__WRAPPER__.scrollTop = (scrollHeight - height) * (moveY / (height - yBar))
this.__Y__.style.transform = `translateY(${moveY}px)`
return moveY
}
_fireReachEnd(action = 'reach-bottom') {
var { delay } = this.props
var { scrollHeight, height } = this.state
var top = this.__WRAPPER__.scrollTop
var now = Date.now()
if (now - this.__last__ > delay) {
if (action === 'reach-bottom') {
if (height + top < scrollHeight) {
return
}
} else {
if (top > 0) {
return
}
}
this.__last__ = now
this.dispatchEvent(new CustomEvent(action))
}
}
mounted() {
// 初始化滚动条的位置和长度
this._initFn = ev => {
// 需要减去因为隐藏原生滚动条修正的18像素
let width = this.offsetWidth
let height = this.offsetHeight
let clientWidth = this.__WRAPPER__.clientWidth
let clientHeight = this.__WRAPPER__.clientHeight
let scrollWidth = this.__WRAPPER__.scrollWidth
let scrollHeight = this.__WRAPPER__.scrollHeight
let yBar = 50 // 滚动条的高度
let xBar = 50 // 滚动条的宽度
let { widthFixed, heightFixed } = this.state
let needFixed = false
let style = ''
// 已经修正过宽度
if (widthFixed > 0) {
needFixed = scrollHeight === clientHeight
widthFixed = needFixed ? 0 : widthFixed
} else {
// 高度超出, 说明出现横向滚动条
if (scrollHeight > clientHeight) {
// width - clientWidth 等于滚动条的宽度, 下同
needFixed = true
widthFixed = width + (width - clientWidth)
}
}
if (heightFixed > 0) {
needFixed = scrollWidth === clientWidth
heightFixed = needFixed ? 0 : heightFixed
} else {
if (scrollWidth > clientWidth) {
needFixed = true
heightFixed = height + (height - clientHeight)
}
}
this.state.widthFixed = widthFixed
this.state.heightFixed = heightFixed
if (needFixed) {
if (widthFixed > 0) {
style += `width:${widthFixed}px;`
}
if (heightFixed > 0) {
style += `height:${heightFixed}px;`
}
this.__WRAPPER__.style.cssText = style
// 需要修正视图容器, 修正完之后, 需要重新获取滚动宽高
scrollWidth = this.__WRAPPER__.scrollWidth
scrollHeight = this.__WRAPPER__.scrollHeight
}
// 修正由于未知原因,导致父容器产生滚动距离
// 导致的内容被遮挡的bug
this.__WRAPPER__.parentNode.scrollTop = 0
yBar = (height * (height / scrollHeight)) >> 0
xBar = (width * (width / scrollWidth)) >> 0
if (yBar < 50) {
yBar = 50
}
if (xBar < 50) {
xBar = 50
}
// 100%或主体高度比滚动条还短时不显示
if (xBar >= width) {
xBar = 0
}
if (yBar >= height) {
yBar = 0
}
this.state.height = height
this.state.width = width
this.state.scrollHeight = scrollHeight
this.state.scrollWidth = scrollWidth
this.state.yBar = yBar
this.state.xBar = xBar
this.__X__.style.width = xBar + 'px'
this.__Y__.style.height = yBar + 'px'
}
// 鼠标滚动事件
this._scrollFn = $.catch(this.__WRAPPER__, 'scroll', ev => {
// 拖拽时忽略滚动事件
if (this._active) {
return
}
var { axis } = this.props
var { xBar, yBar, thumbX, thumbY, scrollHeight, scrollWidth, width, height } = this.state
var currTop = this.__WRAPPER__.scrollTop
var currLeft = this.__WRAPPER__.scrollLeft
// x轴 y轴 都为0时, 不作任何处理
if (xBar === 0 && yBar === 0) {
return
}
//
if (axis === 'y' || axis === 'xy') {
if (yBar) {
// 修正滚动条的位置
// 滚动比例 y 滚动条的可移动距离
let fixedY = (currTop / (scrollHeight - height)) * (height - yBar)
fixedY = fixedY >> 0
if (fixedY !== thumbY) {
this.state.thumbY = fixedY
this.__Y__.style.transform = `translateY(${fixedY}px)`
if (Math.abs(fixedY - thumbY) > this.props.distance) {
this._fireReachEnd(fixedY > thumbY ? 'reach-bottom' : 'reach-top')
}
}
}
}
if (axis === 'x' || axis === 'xy') {
if (xBar) {
// 修正滚动条的位置
// 滚动比例 x 滚动条的可移动距离
let fixedX = (currLeft / (scrollWidth - width)) * (width - xBar)
fixedX = fixedX >> 0
if (fixedX !== thumbX) {
this.state.thumbX = fixedX
this.__X__.style.transform = `translateX(${fixedX}px)`
}
}
}
this.dispatchEvent(new CustomEvent('scroll'))
})
let startX,
startY,
moveX,
moveY,
mousemoveFn = ev => {
let { thumbY, thumbX } = this.state
if (startX !== undefined) {
moveX = this._fetchScrollX(thumbX + ev.pageX - startX)
}
if (startY !== undefined) {
moveY = this._fetchScrollY(thumbY + ev.pageY - startY)
}
},
mouseupFn = ev => {
if (Math.abs(ev.pageY - startY) > this.props.distance) {
this._fireReachEnd(ev.pageY > startY ? 'reach-bottom' : 'reach-top')
}
startX = undefined
startY = undefined
this.state.thumbX = moveX || 0
this.state.thumbY = moveY || 0
delete this._active
$.unbind(document, 'mousemove', mousemoveFn)
$.unbind(document, 'mouseup', mouseupFn)
}
this._yBarFn = $.bind(this.__Y__, 'mousedown', ev => {
startY = ev.pageY
this._active = true
$.bind(document, 'mousemove', mousemoveFn)
$.bind(document, 'mouseup', mouseupFn)
})
this._xBarFn = $.bind(this.__X__, 'mousedown', ev => {
startX = ev.pageX
this._active = true
$.bind(document, 'mousemove', mousemoveFn)
$.bind(document, 'mouseup', mouseupFn)
})
this.__observer = new ResizeObserver(this._initFn)
this.__observer.observe(this.__CONTENT__)
}
unmounted() {
this.__observer.disconnect()
$.unbind(this.__X__, 'mousedown', this._xBarFn)
$.unbind(this.__Y__, 'mousedown', this._yBarFn)
$.unbind(this.__WRAPPER__, 'scroll', this._scrollFn)
}
watch() {
switch (name) {
case 'axis':
if (val) {
if (AXIS_LIST.includes(val)) {
this.props.axis = val
} else {
this.removeAttribute(name)
}
} else {
this.props.axis = 'xy'
}
break
case 'delay':
this.props.delay = +val || 1000
break
case 'distance':
this.props.distance = +val || 1
break
}
}
}
</script>
wcui是一套基于`Web Components`的UI组件库, 宗旨是追求简单、实用、不花哨。
JavaScript 95.2%
CSS 4.8%