Skip to content

Commit

Permalink
Feat(sticky-scroller): 增加 v-sticky-scroller 指令 (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lruihao committed Sep 24, 2023
1 parent df290b5 commit 96754f8
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 14 deletions.
22 changes: 13 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Element UI 的表格组件在使用时,如果表格内容过多,表格会出

- [x] 支持表头吸顶 (v-sticky-header)
- [x] 支持表尾合计行吸底 (v-sticky-footer)
- [ ] 支持滚动条吸底 (v-sticky-h-scroll)
- [ ] 支持横向滚动条吸底 (v-sticky-scroller)
- [x] 支持高度自适应 (v-height-adaptive)

## 安装
Expand Down Expand Up @@ -73,18 +73,22 @@ export default {
```html
<el-table v-sticky-header>...</el-table>
<el-table v-sticky-footer>...</el-table>
<el-table v-sticky-h-scroll>...</el-table>
<el-table v-sticky-scroller>...</el-table>
<el-table v-height-adaptive>...</el-table>
```

## 表格属性
## 指令参数

| 参数 | 说明 | 类型 | 默认值 |
| ------------------- | ------------------ | ------------------------ | --------------------- |
| `v-sticky-header` | 表头吸顶指令 | `Object[Number, String]` | `{ offsetTop: 0 }` |
| `v-sticky-footer` | 表尾合计行吸底指令 | `Object[Number, String]` | `{ offsetBottom: 0 }` |
| `v-sticky-h-scroll` | 滚动条吸底指令 | `Object[Number, String]` | `{ offsetBottom: 0 }` |
| `v-height-adaptive` | 高度自适应指令 | `Object[Number]` | `{ offsetBottom: 0 }` |
| 指令 | 说明 | 修饰符 | 类型 | 默认值 |
| ------------------- | ------------------ | --------- | ------------------------ | --------------------- |
| `v-sticky-header` | 表头吸顶指令 | `.always` | `Object{Number, String}` | `{ offsetTop: 0 }` |
| `v-sticky-footer` | 表尾合计行吸底指令 | `.always` | `Object{Number, String}` | `{ offsetBottom: 0 }` |
| `v-sticky-scroller` | 横向滚动条吸底指令 | `.always` | `Object{Number, String}` | `{ offsetBottom: 0 }` |
| `v-height-adaptive` | 高度自适应指令 | - | `Object{Number}` | `{ offsetBottom: 0 }` |

`v-sticky-header``v-sticky-footer` 已内置滚动条吸底相同功能,无需重复使用 `v-sticky-scroller` 指令。

横向滚动条默认显示方式为 `hover`,可通过设置修饰符为 `.always` 来修改为一直显示。

## Project setup

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"serve": "npm run gr && vue-cli-service serve"
},
"dependencies": {
"gemini-scrollbar": "^1.5.3",
"resize-observer-polyfill": "^1.5.0",
"throttle-debounce": "^1.0.1"
},
Expand Down
29 changes: 29 additions & 0 deletions src/directives/css/sticky-scroller.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Global styles for el-table with directive v-sticky-scroller
@import 'gemini-scrollbar/gemini-scrollbar.css';

.el-table[data-sticky-scroll] {
overflow: visible !important;

.el-table__body-wrapper {
&::-webkit-scrollbar {
height: 0;
}
}

&:hover,
&:focus,
&:active {
.el-table-horizontal-scrollbar .gm-scrollbar {
opacity: 1;
}
}

.el-table-horizontal-scrollbar {
position: sticky;
height: 10px;
width: 100%;
bottom: 0;
z-index: 4;
transition: all 0.3s ease;
}
}
9 changes: 7 additions & 2 deletions src/directives/index.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
import StickyHeader from './sticky-header'
import StickyFooter from './sticky-footer'
import HeightAdaptive from './height-adaptive'
import StickyScroller from './sticky-scroller'
import './css/sticky-scroller.scss'

export {
StickyHeader,
StickyFooter,
StickyScroller,
HeightAdaptive,
}

export default {
/**
* Install directives
* @todo sticky horizontal scroll bar
* @param {Constructor} Vue Vue Constructor
* @param {Object} [options] options from Vue.use
* @param {Object} [options.StickyHeader] options for v-sticky-header
* @param {Number|String} [options.StickyHeader.offsetTop=0] the top offset of the table header
*
* @param {Object} [options.StickyFooter] options for v-sticky-footer
* @param {Number|String} [options.StickyFooter.offsetBottom=0] the bottom offset of the table footer
*
* @param {Object} [options.HeightAdaptive] options for v-height-adaptive
* @param {Number} [options.HeightAdaptive.offsetBottom=0] the offset of the table from the bottom of the page
*
* @example <el-table v-sticky-header="{ offsetTop: 0 }">...</el-table>
* @example <el-table v-sticky-footer="{ offsetBottom: 0 }">...</el-table>
* @example <el-table v-sticky-h-scroll="{ offsetBottom: 0 }">...</el-table>
* @example <el-table v-sticky-scroller="{ offsetBottom: 0 }">...</el-table>
* @example <el-table v-height-adaptive="{ offsetBottom: 0 }" height="100px">...</el-table>
*/
install(Vue, options = {}) {
Expand All @@ -34,6 +38,7 @@ export default {
} = options
Vue.directive(StickyHeader.name, new StickyHeader(headerOptions).init())
Vue.directive(StickyFooter.name, new StickyFooter(footerOptions).init())
Vue.directive(StickyScroller.name, new StickyScroller().init())
Vue.directive(HeightAdaptive.name, new HeightAdaptive(adaptiveOptions).init())
},
}
19 changes: 19 additions & 0 deletions src/directives/sticky-scroller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { checkElTable } from '@/utils'
import Scroller from '@/utils/scroller'

export default class StickyScroller {
static name = 'StickyScroller'

/**
* Init directive config for Vue
* @returns {Object} directive config
*/
init() {
return {
inserted: (el, binding, vnode) => {
checkElTable(binding, vnode)
el.scroller = new Scroller(el, binding, vnode)
},
}
}
}
91 changes: 91 additions & 0 deletions src/utils/scroller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// reference https://github.com/noeldelgado/gemini-scrollbar
import GeminiScrollbar from 'gemini-scrollbar'
import { throttle } from 'throttle-debounce'

const THROTTLE_TIME = 1000 / 60

export default class Scroller {
constructor(el, binding, vnode) {
this.#createScroller(el, binding, vnode)
}

/**
* Create custom horizontal scrollbar for el-table
* @param {Element} el el-table element
* @param {Object} binding binding
* @param {Object} vnode vnode
*/
async #createScroller(el, binding, vnode) {
// create scroller only once for the same el-table
if (el.scroller) {
return
}
// wait for el-table render
await vnode.componentInstance.$nextTick()
// check if el-table has horizontal scrollbar
if (el.querySelector('.is-scrolling-none')) {
return
}

el.dataset.stickyScroll = ''
const tableBodyWrapperEl = el.querySelector('.el-table__body-wrapper')
// create scroller
const scroller = document.createElement('div')
scroller.classList.add('el-table-horizontal-scrollbar')
// set scroller content width to .el-table__body-wrapper table width
const content = document.createElement('div')
content.style.width = `${tableBodyWrapperEl.querySelector('table').offsetWidth}px`
scroller.appendChild(content)
el.appendChild(scroller)

this.#initScrollBar(binding, scroller)
this.#initScrollSyncHandler(tableBodyWrapperEl)
}

/**
* Init scroll bar
* @param {Object} binding binding
* @param {Element} scroller scroller element
*/
#initScrollBar(binding, scroller) {
const { always = false } = binding.modifiers
this.scrollbar = new GeminiScrollbar({
element: scroller,
forceGemini: true,
autoshow: !always,
}).create()
// remove vertical scrollbar
this.scrollbar.element.querySelector('.gm-scrollbar.-vertical').remove()
}

/**
* Init scroll sync handler
* @param {Element} tableBodyWrapperEl .el-table__body-wrapper element
*/
#initScrollSyncHandler(tableBodyWrapperEl) {
const scrollViewEl = this.scrollbar.getViewElement()

// sync tableBodyWrapperEl horizontal scroll to scrollView
tableBodyWrapperEl.addEventListener('scroll', throttle(THROTTLE_TIME, () => {
scrollViewEl.scrollLeft = tableBodyWrapperEl.scrollLeft
}))
// sync scrollViewEl horizontal scroll to tableBodyWrapperEl
scrollViewEl.addEventListener('scroll', throttle(THROTTLE_TIME, () => {
tableBodyWrapperEl.scrollLeft = scrollViewEl.scrollLeft
}))
}

/**
* Recalculate the viewbox and scrollbar dimensions
*/
update() {
this.scrollbar.update()
}

/**
* Unbind the events and remove the custom scrollbar elements
*/
destroy() {
this.scrollbar.destroy()
}
}
6 changes: 3 additions & 3 deletions src/utils/sticky.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { convertToPx, checkElTable } from '@/utils'
import Scroller from '@/utils/scroller'

/**
* @class Sticky
Expand Down Expand Up @@ -116,10 +117,8 @@ export default class Sticky {
* @private
*/
async #stackStickyColumns(el, binding, vnode) {
const { componentInstance: $table } = vnode

// wait for el-table render
await $table.$nextTick()
await vnode.componentInstance.$nextTick()

const { tableCell } = this.#getStickyWrapper(el, binding)

Expand All @@ -139,6 +138,7 @@ export default class Sticky {
checkElTable(binding, vnode)
// set data-sticky-* attribute for el-table
el.dataset[this.#target.replace(/^\S/, s => s.toLowerCase())] = ''
el.scroller = new Scroller(el, binding, vnode)
},
update: (el, binding, vnode) => {
this.#stackStickyColumns(el, binding, vnode)
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3361,6 +3361,11 @@ functional-red-black-tree@^1.0.1:
resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==

gemini-scrollbar@^1.5.3:
version "1.5.3"
resolved "https://registry.npmjs.org/gemini-scrollbar/-/gemini-scrollbar-1.5.3.tgz#7abc916e103e11f983f15856ef8c583cedef95cf"
integrity sha512-3Q4SrxkJ+ei+I5PlcRZCfPePv3EduP7xusOWp7Uw0+XywEWred7Nq9hoaP2IQh1vRjoidaVODV3rO3icFH/e5A==

gensync@^1.0.0-beta.2:
version "1.0.0-beta.2"
resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
Expand Down

0 comments on commit 96754f8

Please sign in to comment.