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`
+
+
+
+ `
}
}