From ed727a36b82ea5738102a9e3221d6daefa2902a4 Mon Sep 17 00:00:00 2001 From: yutent Date: Thu, 23 Mar 2023 16:14:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=BB=9A=E5=8A=A8=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Readme.md | 2 +- package.json | 2 +- src/form/dropdown.js | 22 ++ src/scroll/index.js | 594 ++++++++++++++++++++++++++++--------------- 4 files changed, 420 insertions(+), 200 deletions(-) create mode 100644 src/form/dropdown.js 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..a8c13dd 100644 --- a/src/scroll/index.js +++ b/src/scroll/index.js @@ -4,258 +4,456 @@ * @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 = { + width: 0, // 滚动组件的真实宽度(可视宽高) + height: 0, // 滚动组件的真实高度(可视宽高) + widthFixed: 0, // 滚动组件的宽度修正值 + heightFixed: 0, // 滚动组件的高度修正值 + scrollWidth: 0, // 滚动组件的滚动宽度 + scrollHeight: 0, // 滚动组件的滚动高度 + 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 - } - } - 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 + __init__(ev) { + // + let width = this.offsetWidth + let height = this.offsetHeight + let scrollWidth = this.scrollWidth + let scrollHeight = this.scrollHeight + let clientWidth = this.$refs.box.clientWidth + let clientHeight = this.$refs.box.clientHeight + + let yBar = 50 // 滚动条的高度 + let xBar = 50 // 滚动条的宽度 + let { widthFixed, heightFixed } = this.cache + let needFixed = false + let style = '' + + this.scrollLeft = 0 + this.scrollTop = 0 + + // 已经修正过宽度 + if (widthFixed > 0) { + needFixed = scrollHeight === clientHeight + widthFixed = needFixed ? 0 : widthFixed } else { - start = e.clientX - cur = this.scrollLeft + // 高度超出, 说明出现横向滚动条 + if (scrollHeight > clientHeight) { + // width - clientWidth 等于滚动条的宽度, 下同 + needFixed = true + widthFixed = width + (width - clientWidth) + } } - 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) + if (heightFixed > 0) { + needFixed = scrollWidth === clientWidth + heightFixed = needFixed ? 0 : heightFixed + } else { + if (scrollWidth > clientWidth) { + needFixed = true + heightFixed = height + (height - clientHeight) } - }) + } - this.onmouseup = bind(document, 'mouseup', () => { - this.dragging = false - if (!this.hovering) { - this.$refs.vertical.style.opacity = 0 - this.$refs.horizon.style.opacity = 0 + this.cache.widthFixed = widthFixed + this.cache.heightFixed = heightFixed + + if (needFixed) { + if (widthFixed > 0) { + style += `width:${widthFixed}px;` } - 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') + + if (heightFixed > 0) { + style += `height:${heightFixed}px;` } + this.$refs.box.style.cssText = style + + // 需要修正视图容器, 修正完之后, 需要重新获取滚动宽高 + scrollWidth = this.$refs.box.scrollWidth + scrollHeight = this.$refs.box.scrollHeight } + // 修正由于未知原因,导致父容器产生滚动距离 + // 导致的内容被遮挡的bug + this.$refs.box.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.cache.height = height + this.cache.width = width + this.cache.scrollHeight = scrollHeight + this.cache.scrollWidth = scrollWidth + this.cache.yBar = yBar + this.cache.xBar = xBar + + this.$refs.x.style.width = xBar + 'px' + this.$refs.y.style.height = yBar + 'px' } - 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.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)) + + _fetchScrollX(moveX) { + var { scrollWidth, width, xBar } = this.cache + + 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 } - #watchResize() { - const slotList = this.$refs.slot.assignedNodes() - if (slotList) { - slotList.forEach(element => { - if (element.nodeType === 1) { - this.resizeObserver.observe(element) + + _fetchScrollY(moveY) { + var { scrollHeight, height, yBar } = this.cache + + 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') { + var delay = this.delay + var { scrollHeight, height } = this.cache + var top = this.$refs.box.scrollTop + var 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 + } + var axis = this.axis + var { + xBar, + yBar, + thumbX, + thumbY, + scrollHeight, + scrollWidth, + width, + height + } = this.cache + var currTop = this.$refs.box.scrollTop + var currLeft = this.$refs.box.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.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 = (currLeft / (scrollWidth - width)) * (width - xBar) + + fixedX = fixedX >> 0 + + 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` +
+
+
+
+
+
+
+ ` } }