diff --git a/Readme.md b/Readme.md index 64c5892..3c8841c 100644 --- a/Readme.md +++ b/Readme.md @@ -43,7 +43,7 @@ - [ ] `wc-datepicker`日期选择器 - [ ] `wc-timepicker`时间选择器 - [x] `wc-code`代码高亮插件 -- [ ] `wc-scroll`滚动组件 +- [x] `wc-scroll`滚动组件 - [ ] `wc-silder`滑块组件 - [ ] `wc-progress`进度条组件 - [ ] `wc-tree`树形菜单组件 diff --git a/package.json b/package.json index d324cf8..df00c48 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,6 @@ "author": "yutent", "license": "MIT", "devDependencies": { - "@bd/wcui-cli": "^1.1.0" + "@bd/wcui-cli": "^1.2.0" } } diff --git a/src/form/dropdown.js b/src/form/dropdown.js new file mode 100644 index 0000000..d2c2186 --- /dev/null +++ b/src/form/dropdown.js @@ -0,0 +1,22 @@ +import { nextTick, css, html, Component } from '@bd/core' + +class Dropdown extends Component { + mounted() { + console.log('Dropdown: ', this.$refs) + } + + foo() {} + + render() { + return html` +
+
+ +
+
+
${this.foo}
+ ` + } +} + +Dropdown.reg('dropdown') diff --git a/src/scroll/index.js b/src/scroll/index.js index 4293eef..038be57 100644 --- a/src/scroll/index.js +++ b/src/scroll/index.js @@ -4,258 +4,414 @@ * @date 2023/03/20 15:17:25 */ -import { css, bind, html, unbind, Component } from '@bd/core' +import { css, html, bind, unbind, Component } from '@bd/core' class Scroll extends Component { static props = { - axis: 'xy', - distance: 0 + axis: 'xy', // 滚动方向, 默认x轴和y轴都可以滚动 + delay: 1000, // 节流防抖延迟 + distance: 1 // 触发距离阀值, 单位像素 } - dragging = false - hovering = false + static styles = [ css` :host { position: relative; - overflow: hidden; display: block; - } - .scroller { - position: relative; - height: 100%; width: 100%; - overflow: auto; - &::-webkit-scrollbar { - display: none; - } - scrollbar-width: none; //火狐专属 - } - .scroll-bar { - position: absolute; - background: #909399; - width: 0; - height: 0; - opacity: 0; - border-radius: 4px; - cursor: pointer; - transition: opacity ease-in-out 0.2s; + height: 100%; - &.vertical { - width: 8px; - top: 0; - right: 0; + .container { + overflow: hidden; + position: relative; + width: 100%; + height: 100%; } - &.horizon { - height: 8px; - bottom: 0; - left: 0; + + .wrapper { + overflow: auto; + scrollbar-width: none; + width: 100%; + height: 100%; + scrollbar-width: 0; + + &::-webkit-scrollbar { + display: none; + } } - &:hover { - opacity: 0.5 !important; + .content { + width: fit-content; + height: fit-content; } } - :host([disabled]) { - overflow: hidden !important; - .scroll-bar { - visibility: hidden; + `, + css` + /* 横向 */ + + .is-horizontal, + .is-vertical { + 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; + } + } + } + `, + css` + :host(:hover) { + .is-horizontal, + .is-vertical { + visibility: visible; + opacity: 1; + } + } + :host([axis='x']) { - overflow-x: auto; - overflow-y: hidden; - .vertical { + .wrapper { + overflow-y: hidden; + } + .is-vertical { display: none; } } :host([axis='y']) { - overflow-y: auto; - overflow-x: hidden; - .horizon { + .wrapper { + overflow-x: hidden; + } + .is-horizontal { + display: none; + } + } + :host([disabled]) { + .wrapper { + overflow: hidden; + } + .is-vertical, + .is-horizontal { display: none; } } ` ] + + stamp = 0 + + cache = { + xBar: 0, // 横轴长度 + yBar: 0, // 纵轴长度 + thumbX: 0, //横向条滚动距离 + thumbY: 0 // 纵向条滚动距离 + } + get scrollTop() { - return this.$refs.scroller.scrollTop + return this.$refs.box?.scrollTop || 0 } - set scrollTop(val) { - this.$refs.scroller.scrollTop = val - } - get scrollHeight() { - return this.$refs.scroller.scrollHeight - } - set scrollHeight(val) { - this.$refs.scroller.scrollHeight = val + + set scrollTop(n) { + n = +n + if (n === n) { + this.$refs.box.scrollTop = n + } } + get scrollLeft() { - return this.$refs.scroller.scrollLeft + return this.$refs.box?.scrollLeft || 0 } - set scrollLeft(val) { - this.$refs.scroller.scrollLeft = val + + set scrollLeft(n) { + n = +n + if (n === n) { + this.$refs.box.scrollLeft = n + } } + + get scrollHeight() { + return this.$refs.box?.scrollHeight + } + get scrollWidth() { - return this.$refs.scroller.scrollWidth - } - set scrollWidth(val) { - this.$refs.scroller.scrollWidth = val - } - render() { - return html` -
- -
-
-
- ` + return this.$refs.box?.scrollWidth } - 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 + __init__(ev) { + // + let width = this.offsetWidth + let height = this.offsetHeight + let scrollWidth = this.scrollWidth + let scrollHeight = this.scrollHeight + + let yBar = 50 // 滚动条的高度 + let xBar = 50 // 滚动条的宽度 + + this.scrollLeft = 0 + this.scrollTop = 0 + + // 修正由于未知原因,导致父容器产生滚动距离 + // 导致的内容被遮挡的bug + // 重构后待观察, 暂时注释掉这行(2023/3/23) + // this.$refs.box.parentNode.scrollTop = 0 + + yBar = (height * (height / scrollHeight)) >> 0 + xBar = (width * (width / scrollWidth)) >> 0 + + if (yBar < 50) { + yBar = 50 } - } - 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 + if (xBar < 50) { + xBar = 50 } - 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) - } - }) + // 100%或主体高度比滚动条还短时不显示 + if (xBar >= width) { + xBar = 0 + } + if (yBar >= height) { + yBar = 0 + } - 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.transform = `translateY(${ - (clientHeight - verticalHeight) * verticalProgress - }px)` - horizon.style.transform = `translateX(${ - (clientWidth - horizonWidth) * horizonProgress - }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 + this.cache.yBar = yBar + this.cache.xBar = xBar + + if (xBar > 0) { + this.$refs.x.parentNode.style.display = 'flex' + this.$refs.x.style.width = xBar + 'px' } else { - this.verticalHeight = 0 + this.$refs.x.parentNode.style.display = 'none' } - if (clientWidth !== scrollWidth) { - this.horizonWidth = (clientWidth / scrollWidth) * clientWidth + if (yBar > 0) { + this.$refs.y.parentNode.style.display = 'flex' + this.$refs.y.style.height = yBar + 'px' } else { - this.horizonWidth = 0 - } - if (axis.includes('x')) { - horizon.style.width = this.horizonWidth + 'px' - bind(horizon, 'mousedown', this.onmousedown.bind(this)) - } - if (axis.includes('y')) { - vertical.style.height = this.verticalHeight + 'px' - bind(vertical, 'mousedown', this.onmousedown.bind(this)) + this.$refs.y.parentNode.style.display = 'none' } } - #watchResize() { - const slotList = this.$refs.slot.assignedNodes() - if (slotList) { - slotList.forEach(element => { - if (element.nodeType === 1) { - this.resizeObserver.observe(element) + + _fetchScrollX(moveX) { + let { xBar } = this.cache + let { scrollWidth, offsetWidth: width } = this + + if (moveX < 0) { + moveX = 0 + } else if (moveX > width - xBar) { + moveX = width - xBar + } + this.scrollLeft = (scrollWidth - width) * (moveX / (width - xBar)) + this.$refs.x.style.transform = `translateX(${moveX}px)` + + return moveX + } + + _fetchScrollY(moveY) { + let { yBar } = this.cache + let { scrollHeight, offsetHeight: height } = this + + if (moveY < 0) { + moveY = 0 + } else if (moveY > height - yBar) { + moveY = height - yBar + } + + this.scrollTop = (scrollHeight - height) * (moveY / (height - yBar)) + this.$refs.y.style.transform = `translateY(${moveY}px)` + return moveY + } + + _fireReachEnd(action = 'reach-bottom') { + let delay = this.delay + let { scrollHeight, offsetHeight: height } = this + let top = this.scrollTop + let now = Date.now() + + if (now - this.stamp > delay) { + if (action === 'reach-bottom') { + if (height + top < scrollHeight) { + return } - }) + } else { + if (top > 0) { + return + } + } + + this.stamp = now + this.$emit(action) } } + mounted() { - this.$on('mouseenter', this.onmouseenter) - this.$on('mouseleave', this.onmouseleave) - this.onscroll = bind( - this.$refs.scroller, - 'scroll', - this.onscroll.bind(this) - ) - this.resizeObserver = new ResizeObserver(() => { - this.initScrollBar() - this.onscroll() + let startX, + startY, + moveX, + moveY, + mousemoveFn = ev => { + let { thumbY, thumbX } = this.cache + 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.distance) { + this._fireReachEnd(ev.pageY > startY ? 'reach-bottom' : 'reach-top') + } + startX = undefined + startY = undefined + this.cache.thumbX = moveX || 0 + this.cache.thumbY = moveY || 0 + delete this._active + unbind(document, 'mousemove', mousemoveFn) + unbind(document, 'mouseup', mouseupFn) + } + + // 鼠标滚动事件 + this._scrollFn = bind(this.$refs.box, 'scroll', ev => { + ev.stopPropagation() + // 拖拽时忽略滚动事件 + if (this._active) { + return + } + let { xBar, yBar, thumbX, thumbY } = this.cache + let { + axis, + scrollHeight, + scrollWidth, + offsetHeight: height, + offsetWidth: width, + scrollTop, + scrollLeft + } = this + + // x轴 y轴 都为0时, 不作任何处理 + if (xBar === 0 && yBar === 0) { + return + } + + // + if (axis === 'y' || axis === 'xy') { + if (yBar) { + // 修正滚动条的位置 + // 滚动比例 y 滚动条的可移动距离 + let fixedY = ~~( + (scrollTop / (scrollHeight - height)) * + (height - yBar) + ) + + if (fixedY !== thumbY) { + this.cache.thumbY = fixedY + this.$refs.y.style.transform = `translateY(${fixedY}px)` + + if (Math.abs(fixedY - thumbY) > this.distance) { + this._fireReachEnd(fixedY > thumbY ? 'reach-bottom' : 'reach-top') + } + } + } + } + + if (axis === 'x' || axis === 'xy') { + if (xBar) { + // 修正滚动条的位置 + // 滚动比例 x 滚动条的可移动距离 + let fixedX = ~~((scrollLeft / (scrollWidth - width)) * (width - xBar)) + + if (fixedX !== thumbX) { + this.cache.thumbX = fixedX + this.$refs.x.style.transform = `translateX(${fixedX}px)` + } + } + } + + this.$emit('scroll') }) - this.#watchResize() - this.$refs.slot.addEventListener('slotchange', () => this.#watchResize()) + + this._yBarFn = bind(this.$refs.y, 'mousedown', ev => { + startY = ev.pageY + + this._active = true + + bind(document, 'mousemove', mousemoveFn) + bind(document, 'mouseup', mouseupFn) + }) + + this._xBarFn = bind(this.$refs.x, 'mousedown', ev => { + startX = ev.pageX + this._active = true + + bind(document, 'mousemove', mousemoveFn) + bind(document, 'mouseup', mouseupFn) + }) + + this.__observer = new ResizeObserver(this.__init__.bind(this)) + this.__observer.observe(this.$refs.cont) } 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) + this.__observer.disconnect() + } + + render() { + return html` +
+
+
+
+
+
+ +
+
+ +
+ ` } }