فهرست منبع

Add scrollbar

qingwei.li 8 سال پیش
والد
کامیت
5aea5eced3

+ 1 - 0
.travis.yml

@@ -5,6 +5,7 @@ cache:
   directories:
   - $HOME/.npm
   - $HOME/.yarn-cache
+  - node_modules
 before_install:
 - curl -o- -L https://yarnpkg.com/install.sh | bash
 - export PATH=$HOME/.yarn/bin:$PATH

+ 2 - 1
components.json

@@ -54,5 +54,6 @@
   "rate": "./packages/rate/index.js",
   "steps": "./packages/steps/index.js",
   "step": "./packages/step/index.js",
-  "carousel": "./packages/carousel/index.js"
+  "carousel": "./packages/carousel/index.js",
+  "scrollbar": "./packages/scrollbar/index.js"
 }

+ 65 - 44
packages/date-picker/src/basic/time-spinner.vue

@@ -1,56 +1,65 @@
 <template>
   <div class="el-time-spinner" :class="{ 'has-seconds': showSeconds }">
-    <div
-      @mouseenter="emitSelectRange('hours')"
-      @mousewheel="handleScroll('hour')"
+    <el-scrollbar
+      @mouseenter.native="emitSelectRange('hours')"
+      @mousewheel.native="handleScroll('hour')"
       class="el-time-spinner__wrapper"
+      wrap-style="max-height: inherit;"
+      view-class="el-time-spinner__list"
+      noresize
+      tag="ul"
       ref="hour">
-      <ul class="el-time-spinner__list">
-        <li
-          @click="handleClick('hours', { value: hour, disabled: disabled }, true)"
-          v-for="(disabled, hour) in hoursList"
-          track-by="hour"
-          class="el-time-spinner__item"
-          :class="{ 'active': hour === hours, 'disabled': disabled }"
-          v-text="hour"></li>
-      </ul>
-    </div>
-    <div
-      @mouseenter="emitSelectRange('minutes')"
-      @mousewheel="handleScroll('minute')"
+      <li
+        @click="handleClick('hours', { value: hour, disabled: disabled }, true)"
+        v-for="(disabled, hour) in hoursList"
+        track-by="hour"
+        class="el-time-spinner__item"
+        :class="{ 'active': hour === hours, 'disabled': disabled }"
+        v-text="hour"></li>
+    </el-scrollbar>
+    <el-scrollbar
+      @mouseenter.native="emitSelectRange('minutes')"
+      @mousewheel.native="handleScroll('minute')"
       class="el-time-spinner__wrapper"
+      wrap-style="max-height: inherit;"
+      view-class="el-time-spinner__list"
+      noresize
+      tag="ul"
       ref="minute">
-      <ul class="el-time-spinner__list">
-        <li
-          @click="handleClick('minutes', key, true)"
-          v-for="(minute, key) in 60"
-          class="el-time-spinner__item"
-          :class="{ 'active': key === minutes }"
-          v-text="key"></li>
-      </ul>
-    </div>
-    <div
+      <li
+        @click="handleClick('minutes', key, true)"
+        v-for="(minute, key) in 60"
+        class="el-time-spinner__item"
+        :class="{ 'active': key === minutes }"
+        v-text="key"></li>
+    </el-scrollbar>
+    <el-scrollbar
       v-show="showSeconds"
-      @mouseenter="emitSelectRange('seconds')"
-      @mousewheel="handleScroll('second')"
+      @mouseenter.native="emitSelectRange('seconds')"
+      @mousewheel.native="handleScroll('second')"
       class="el-time-spinner__wrapper"
+      wrap-style="max-height: inherit;"
+      view-class="el-time-spinner__list"
+      noresize
+      tag="ul"
       ref="second">
-      <ul class="el-time-spinner__list">
-        <li
-          @click="handleClick('seconds', key, true)"
-          v-for="(second, key) in 60"
-          class="el-time-spinner__item"
-          :class="{ 'active': key === seconds }"
-          v-text="key"></li>
-      </ul>
-    </div>
+      <li
+        @click="handleClick('seconds', key, true)"
+        v-for="(second, key) in 60"
+        class="el-time-spinner__item"
+        :class="{ 'active': key === seconds }"
+        v-text="key"></li>
+    </el-scrollbar>
   </div>
 </template>
 
 <script type="text/babel">
   import { getRangeHours } from '../util';
+  import ElScrollbar from 'element-ui/packages/scrollbar';
 
   export default {
+    components: { ElScrollbar },
+
     props: {
       hours: {
         type: Number,
@@ -78,7 +87,7 @@
         if (!(newVal >= 0 && newVal <= 23)) {
           this.hoursPrivate = oldVal;
         }
-        this.$refs.hour.scrollTop = Math.max(0, (this.hoursPrivate - 2.5) * 32 + 80);
+        this.hourElm.scrollTop = Math.max(0, (this.hoursPrivate - 2.5) * 32 + 80);
         this.$emit('change', { hours: newVal });
       },
 
@@ -86,7 +95,7 @@
         if (!(newVal >= 0 && newVal <= 59)) {
           this.minutesPrivate = oldVal;
         }
-        this.$refs.minute.scrollTop = Math.max(0, (this.minutesPrivate - 2.5) * 32 + 80);
+        this.minuteElm.scrollTop = Math.max(0, (this.minutesPrivate - 2.5) * 32 + 80);
         this.$emit('change', { minutes: newVal });
       },
 
@@ -94,7 +103,7 @@
         if (!(newVal >= 0 && newVal <= 59)) {
           this.secondsPrivate = oldVal;
         }
-        this.$refs.second.scrollTop = Math.max(0, (this.secondsPrivate - 2.5) * 32 + 80);
+        this.secondElm.scrollTop = Math.max(0, (this.secondsPrivate - 2.5) * 32 + 80);
         this.$emit('change', { seconds: newVal });
       }
     },
@@ -102,6 +111,18 @@
     computed: {
       hoursList() {
         return getRangeHours(this.selectableRange);
+      },
+
+      hourElm() {
+        return this.$refs.hour.wrap;
+      },
+
+      minuteElm() {
+        return this.$refs.minute.wrap;
+      },
+
+      secondElm() {
+        return this.$refs.second.wrap;
       }
     },
 
@@ -138,14 +159,14 @@
       handleScroll(type) {
         const ajust = {};
 
-        ajust[`${type}s`] = Math.min(Math.floor((this.$refs[type].scrollTop - 80) / 32 + 3), 59);
+        ajust[`${type}s`] = Math.min(Math.floor((this[`${type}Elm`].scrollTop - 80) / 32 + 3), 59);
         this.$emit('change', ajust);
       },
 
       ajustScrollTop() {
-        this.$refs.hour.scrollTop = Math.max(0, (this.hours - 2.5) * 32 + 80);
-        this.$refs.minute.scrollTop = Math.max(0, (this.minutes - 2.5) * 32 + 80);
-        this.$refs.second.scrollTop = Math.max(0, (this.seconds - 2.5) * 32 + 80);
+        this.hourElm.scrollTop = Math.max(0, (this.hours - 2.5) * 32 + 80);
+        this.minuteElm.scrollTop = Math.max(0, (this.minutes - 2.5) * 32 + 80);
+        this.secondElm.scrollTop = Math.max(0, (this.seconds - 2.5) * 32 + 80);
       }
     }
   };

+ 8 - 4
packages/date-picker/src/panel/time-select.vue

@@ -3,20 +3,22 @@
     <div
       v-show="visible"
       :style="{ width: width + 'px' }"
-      class="el-picker-panel time-select"
-      :class="popperClass">
-      <div class="el-picker-panel__content">
+      :class="popperClass"
+      class="el-picker-panel time-select">
+      <el-scrollbar noresize wrap-class="el-picker-panel__content">
         <div class="time-select-item"
           v-for="item in items"
           :class="{ selected: value === item.value, disabled: item.disabled }"
           :disabled="item.disabled"
           @click="handleClick(item)">{{ item.value }}</div>
-      </div>
+      </el-scrollbar>
     </div>
   </transition>
 </template>
 
 <script type="text/babel">
+  import ElScrollbar from 'element-ui/packages/scrollbar';
+
   const parseTime = function(time) {
     const values = ('' || time).split(':');
     if (values.length >= 2) {
@@ -69,6 +71,8 @@
   };
 
   export default {
+    components: { ElScrollbar },
+
     watch: {
       minTime(val) {
         if (this.value && val && compareTime(this.value, val) === -1) {

+ 8 - 0
packages/scrollbar/index.js

@@ -0,0 +1,8 @@
+import Scrollbar from './src/main';
+
+/* istanbul ignore next */
+Scrollbar.install = function(Vue) {
+  Vue.component(Scrollbar.name, Scrollbar);
+};
+
+export default Scrollbar;

+ 87 - 0
packages/scrollbar/src/bar.js

@@ -0,0 +1,87 @@
+import { on, off } from 'wind-dom/src/event';
+import { renderThumbStyle, BAR_MAP } from './util';
+
+export default {
+  name: 'Bar',
+
+  props: {
+    vertical: Boolean,
+    size: String,
+    move: Number
+  },
+
+  computed: {
+    bar() {
+      return BAR_MAP[this.vertical ? 'vertical' : 'horizontal'];
+    },
+
+    wrap() {
+      return this.$parent.wrap;
+    }
+  },
+
+  render(h) {
+    const { size, move, bar } = this;
+
+    return (
+      <div
+        class={ ['el-scrollbar__bar', 'is-' + bar.key] }
+        onMousedown={ this.clickTrackHandler } >
+        <div
+          ref="thumb"
+          class="el-scrollbar__thumb"
+          onMousedown={ this.clickThumbHandler }
+          style={ renderThumbStyle({ size, move, bar }) }>
+        </div>
+      </div>
+    );
+  },
+
+  methods: {
+    clickThumbHandler(e) {
+      this.startDrag(e);
+      this[this.bar.axis] = (e.currentTarget[this.bar.offset] - (e[this.bar.client] - e.currentTarget.getBoundingClientRect()[this.bar.direction]));
+    },
+
+    clickTrackHandler(e) {
+      const offset = Math.abs(e.target.getBoundingClientRect()[this.bar.direction] - e[this.bar.client]);
+      const thumbHalf = (this.$refs.thumb[this.bar.offset] / 2);
+      const thumbPositionPercentage = ((offset - thumbHalf) * 100 / this.$el[this.bar.offset]);
+
+      this.wrap[this.bar.scroll] = (thumbPositionPercentage * this.wrap[this.bar.scrollSize] / 100);
+    },
+
+    startDrag(e) {
+      e.stopImmediatePropagation();
+      this.cursorDown = true;
+
+      on(document, 'mousemove', this.mouseMoveDocumentHandler);
+      on(document, 'mouseup', this.mouseUpDocumentHandler);
+      document.onselectstart = () => false;
+    },
+
+    mouseMoveDocumentHandler(e) {
+      if (this.cursorDown === false) return;
+      const prevPage = this[this.bar.axis];
+
+      if (!prevPage) return;
+
+      const offset = ((this.$el.getBoundingClientRect()[this.bar.direction] - e[this.bar.client]) * -1);
+      const thumbClickPosition = (this.$refs.thumb[this.bar.offset] - prevPage);
+      const thumbPositionPercentage = ((offset - thumbClickPosition) * 100 / this.$el[this.bar.offset]);
+
+      this.wrap[this.bar.scroll] = (thumbPositionPercentage * this.wrap[this.bar.scrollSize] / 100);
+    },
+
+    mouseUpDocumentHandler(e) {
+      this.cursorDown = false;
+      this[this.bar.axis] = 0;
+      off(document, 'mousemove', this.mouseMoveDocumentHandler);
+      document.onselectstart = null;
+    }
+  },
+
+  destroyed() {
+    off(document, 'mouseup', this.mouseUpDocumentHandler);
+  }
+};

+ 109 - 0
packages/scrollbar/src/main.js

@@ -0,0 +1,109 @@
+// reference https://github.com/noeldelgado/gemini-scrollbar/blob/master/index.js
+
+import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event';
+import * as util from './util';
+import Bar from './bar';
+
+export default {
+  name: 'ElScrollbar',
+
+  components: { Bar },
+
+  props: {
+    wrapStyle: {},
+    wrapClass: {},
+    viewClass: {},
+    viewStyle: {},
+    noresize: Boolean, // 如果 container 尺寸不会发生变化,最好设置它可以优化性能
+    tag: {
+      type: String,
+      default: 'div'
+    }
+  },
+
+  data() {
+    return {
+      sizeWidth: '0',
+      sizeHeight: '0',
+      moveX: 0,
+      moveY: 0
+    };
+  },
+
+  computed: {
+    wrap() {
+      return this.$refs.wrap;
+    }
+  },
+
+  render(h) {
+    let gutter = util.getScrollBarWidth();
+    let style = this.wrapStyle;
+
+    if (gutter) {
+      const gutterWith = `-${gutter}px`;
+
+      if (Array.isArray(this.wrapStyle)) {
+        style = util.toObject(this.wrapStyle);
+        style.marginRight = style.marginBottom = gutterWith;
+      } else if (typeof this.wrapStyle === 'string') {
+        style += `margin-bottom: ${gutterWith}; margin-right: ${gutterWith};`;
+      }
+    }
+    const wrap = (
+      <div
+        ref="wrap"
+        style={ style }
+        onScroll={ this.handleScroll }
+        class={ [this.wrapClass, 'el-scrollbar__wrap'] }>
+        {
+          h(this.tag, {
+            class: ['el-scrollbar__view', this.viewClass],
+            style: this.viewStyle,
+            ref: 'resize'
+          }, this.$slots.default)
+        }
+      </div>
+    );
+    const nodes = ([
+      wrap,
+      <Bar
+        move={ this.moveX }
+        size={ this.sizeWidth }></Bar>,
+      <Bar
+        vertical
+        move={ this.moveY }
+        size={ this.sizeHeight }></Bar>
+    ]);
+    return h('div', { class: 'el-scrollbar' }, nodes);
+  },
+
+  methods: {
+    handleScroll() {
+      const wrap = this.wrap;
+
+      this.moveY = ((wrap.scrollTop * 100) / wrap.clientHeight);
+      this.moveX = ((wrap.scrollLeft * 100) / wrap.clientWidth);
+    },
+
+    update() {
+      let heightPercentage, widthPercentage;
+      const wrap = this.wrap;
+
+      heightPercentage = (wrap.clientHeight * 100 / wrap.scrollHeight);
+      widthPercentage = (wrap.clientWidth * 100 / wrap.scrollWidth);
+
+      this.sizeHeight = (heightPercentage < 100) ? (heightPercentage + '%') : '';
+      this.sizeWidth = (widthPercentage < 100) ? (widthPercentage + '%') : '';
+    }
+  },
+
+  mounted() {
+    this.$nextTick(this.update);
+    !this.noresize && addResizeListener(this.$refs.resize, this.update);
+  },
+
+  destroyed() {
+    !this.noresize && removeResizeListener(this.$refs.resize, this.update);
+  }
+};

+ 65 - 0
packages/scrollbar/src/util.js

@@ -0,0 +1,65 @@
+import Vue from 'vue';
+
+export const BAR_MAP = {
+  vertical: {
+    offset: 'offsetHeight',
+    scroll: 'scrollTop',
+    scrollSize: 'scrollHeight',
+    size: 'height',
+    key: 'vertical',
+    axis: 'Y',
+    client: 'clientY',
+    direction: 'top'
+  },
+  horizontal: {
+    offset: 'offsetWidth',
+    scroll: 'scrollLeft',
+    scrollSize: 'scrollWidth',
+    size: 'width',
+    key: 'horizontal',
+    axis: 'X',
+    client: 'clientX',
+    direction: 'left'
+  }
+};
+
+let scrollBarWidth;
+
+export function getScrollBarWidth() {
+  if (Vue.prototype.$isServer) return 0;
+  if (scrollBarWidth !== undefined) return scrollBarWidth;
+
+  const outer = document.createElement('div');
+  outer.className = 'el-scrollbar__wrap';
+  outer.style.visibility = 'hidden';
+  outer.style.width = '100px';
+  outer.style.position = 'absolute';
+  outer.style.top = '-9999px';
+  document.body.appendChild(outer);
+
+  const widthNoScroll = outer.offsetWidth;
+  outer.style.overflow = 'scroll';
+
+  const inner = document.createElement('div');
+  inner.style.width = '100%';
+  outer.appendChild(inner);
+
+  const widthWithScroll = inner.offsetWidth;
+  outer.parentNode.removeChild(outer);
+
+  return widthNoScroll - widthWithScroll;
+};
+
+export function renderThumbStyle({ move, size, bar }) {
+  const style = {};
+  const translate = `translate${bar.axis}(${ move }%)`;
+
+  style[bar.size] = size;
+  style.transform = translate;
+  style.msTransform = translate;
+  style.webkitTransform = translate;
+
+  return style;
+};
+
+export const toObject = Vue.util.toObject;

+ 8 - 4
packages/select/src/select.vue

@@ -65,8 +65,10 @@
       <el-select-menu
         ref="popper"
         v-show="visible && emptyText !== false">
-        <ul
-          class="el-select-dropdown__list"
+        <el-scrollbar
+          tag="ul"
+          wrap-class="el-select-dropdown__wrap"
+          view-class="el-select-dropdown__list"
           :class="{ 'is-empty': !allowCreate && filteredOptionsCount === 0 }"
           v-show="options.length > 0 && !loading">
           <el-option
@@ -75,7 +77,7 @@
             v-if="showNewOption">
           </el-option>
           <slot></slot>
-        </ul>
+        </el-scrollbar>
         <p class="el-select-dropdown__empty" v-if="emptyText && !allowCreate">{{ emptyText }}</p>
       </el-select-menu>
     </transition>
@@ -89,6 +91,7 @@
   import ElSelectMenu from './select-dropdown.vue';
   import ElOption from './option.vue';
   import ElTag from 'element-ui/packages/tag';
+  import ElScrollbar from 'element-ui/packages/scrollbar';
   import debounce from 'throttle-debounce/debounce';
   import Clickoutside from 'element-ui/src/utils/clickoutside';
   import { addClass, removeClass, hasClass } from 'wind-dom/src/class';
@@ -148,7 +151,8 @@
       ElInput,
       ElSelectMenu,
       ElOption,
-      ElTag
+      ElTag,
+      ElScrollbar
     },
 
     directives: { Clickoutside },

+ 1 - 1
packages/table/src/filter-panel.vue

@@ -163,7 +163,7 @@
     mounted() {
       this.popperElm = this.$el;
       this.referenceElm = this.cell;
-      this.table.$refs.bodyWrapper.addEventListener('scroll', () => {
+      this.table.bodyWrapper.addEventListener('scroll', () => {
         this.updatePopper();
       });
 

+ 13 - 9
packages/table/src/table-body.js

@@ -40,7 +40,7 @@ export default {
             this._l(this.data, (row, $index) =>
               <tr
                 style={ this.rowStyle ? this.getRowStyle(row, $index) : null }
-                key={ this.$parent.rowKey ? this.getKeyOfRow(row, $index) : $index }
+                key={ this.table.rowKey ? this.getKeyOfRow(row, $index) : $index }
                 on-dblclick={ ($event) => this.handleDoubleClick($event, row) }
                 on-click={ ($event) => this.handleClick($event, row) }
                 on-contextmenu={ ($event) => this.handleContextMenu($event, row) }
@@ -54,7 +54,7 @@ export default {
                       on-mouseenter={ ($event) => this.handleCellMouseEnter($event, row) }
                       on-mouseleave={ this.handleCellMouseLeave }>
                       {
-                        column.renderCell.call(this._renderProxy, h, { row, column, $index, store: this.store, _self: this.context || this.$parent.$vnode.context })
+                        column.renderCell.call(this._renderProxy, h, { row, column, $index, store: this.store, _self: this.context || this.table.$vnode.context })
                       }
                     </td>
                   )
@@ -103,6 +103,10 @@ export default {
   },
 
   computed: {
+    table() {
+      return this.$parent.$parent.columns ? this.$parent.$parent : this.$parent;
+    },
+
     data() {
       return this.store.states.data;
     },
@@ -132,7 +136,7 @@ export default {
 
   methods: {
     getKeyOfRow(row, index) {
-      const rowKey = this.$parent.rowKey;
+      const rowKey = this.table.rowKey;
       if (rowKey) {
         return getRowIdentity(row, rowKey);
       }
@@ -171,7 +175,7 @@ export default {
     },
 
     handleCellMouseEnter(event, row) {
-      const table = this.$parent;
+      const table = this.table;
       const cell = getCell(event);
 
       if (cell) {
@@ -190,8 +194,8 @@ export default {
       const cell = getCell(event);
       if (!cell) return;
 
-      const oldHoverState = this.$parent.hoverState;
-      this.$parent.$emit('cell-mouse-leave', oldHoverState.row, oldHoverState.column, oldHoverState.cell, event);
+      const oldHoverState = this.table.hoverState;
+      this.table.$emit('cell-mouse-leave', oldHoverState.row, oldHoverState.column, oldHoverState.cell, event);
     },
 
     handleMouseEnter(index) {
@@ -203,17 +207,17 @@ export default {
     },
 
     handleContextMenu(event, row) {
-      const table = this.$parent;
+      const table = this.table;
       table.$emit('row-contextmenu', row, event);
     },
 
     handleDoubleClick(event, row) {
-      const table = this.$parent;
+      const table = this.table;
       table.$emit('row-dblclick', row, event);
     },
 
     handleClick(event, row) {
-      const table = this.$parent;
+      const table = this.table;
       const cell = getCell(event);
       let column;
       if (cell) {

+ 2 - 10
packages/table/src/table-layout.js

@@ -1,7 +1,3 @@
-import { getScrollBarWidth } from './util';
-
-let GUTTER_WIDTH;
-
 class TableLayout {
   constructor(options) {
     this.table = null;
@@ -21,11 +17,7 @@ class TableLayout {
     this.viewportHeight = null; // Table Height - Scroll Bar Height
     this.bodyHeight = null; // Table Height - Table Header Height
     this.fixedBodyHeight = null; // Table Height - Table Header Height - Scroll Bar Height
-
-    if (GUTTER_WIDTH === undefined) {
-      GUTTER_WIDTH = getScrollBarWidth();
-    }
-    this.gutterWidth = GUTTER_WIDTH;
+    this.gutterWidth = 0;
 
     for (let name in options) {
       if (options.hasOwnProperty(name)) {
@@ -44,7 +36,7 @@ class TableLayout {
   updateScrollY() {
     const height = this.height;
     if (typeof height !== 'string' && typeof height !== 'number') return;
-    const bodyWrapper = this.table.$refs.bodyWrapper;
+    const bodyWrapper = this.table.bodyWrapper;
     if (this.table.$el && bodyWrapper) {
       const body = bodyWrapper.querySelector('.el-table__body');
       this.scrollY = body.offsetHeight > bodyWrapper.offsetHeight;

+ 15 - 8
packages/table/src/table.vue

@@ -18,7 +18,10 @@
         :style="{ width: layout.bodyWidth ? layout.bodyWidth + 'px' : '' }">
       </table-header>
     </div>
-    <div class="el-table__body-wrapper" ref="bodyWrapper" :style="[bodyHeight]">
+    <el-scrollbar
+      class="el-table__body-wrapper"
+      ref="bodyWrapper"
+      :wrap-style="[bodyHeight]">
       <table-body
         :context="context"
         :store="store"
@@ -29,9 +32,9 @@
         :style="{ width: layout.bodyWidth ? layout.bodyWidth - (layout.scrollY ? layout.gutterWidth : 0 ) + 'px' : '' }">
       </table-body>
       <div class="el-table__empty-block" v-if="!data || data.length === 0">
-      <span class="el-table__empty-text"><slot name="empty">{{ emptyText || t('el.table.emptyText') }}</slot></span>
+        <span class="el-table__empty-text"><slot name="empty">{{ emptyText || t('el.table.emptyText') }}</slot></span>
       </div>
-    </div>
+    </el-scrollbar>
     <div class="el-table__fixed" ref="fixedWrapper"
       v-if="fixedColumns.length > 0"
       :style="[
@@ -205,22 +208,22 @@
       },
 
       bindEvents() {
-        const { bodyWrapper, headerWrapper } = this.$refs;
+        const { headerWrapper } = this.$refs;
         const refs = this.$refs;
-        bodyWrapper.addEventListener('scroll', function() {
+        this.bodyWrapper.addEventListener('scroll', function() {
           if (headerWrapper) headerWrapper.scrollLeft = this.scrollLeft;
           if (refs.fixedBodyWrapper) refs.fixedBodyWrapper.scrollTop = this.scrollTop;
           if (refs.rightFixedBodyWrapper) refs.rightFixedBodyWrapper.scrollTop = this.scrollTop;
         });
 
         if (headerWrapper) {
-          mousewheel(headerWrapper, throttle(16, function(event) {
+          mousewheel(headerWrapper, throttle(16, event => {
             const deltaX = event.deltaX;
 
             if (deltaX > 0) {
-              bodyWrapper.scrollLeft = bodyWrapper.scrollLeft + 10;
+              this.bodyWrapper.scrollLeft += 10;
             } else {
-              bodyWrapper.scrollLeft = bodyWrapper.scrollLeft - 10;
+              this.bodyWrapper.scrollLeft -= 10;
             }
           }));
         }
@@ -255,6 +258,10 @@
     },
 
     computed: {
+      bodyWrapper() {
+        return this.$refs.bodyWrapper.wrap;
+      },
+
       shouldUpdateHeight() {
         return typeof this.height === 'number' ||
           this.fixedColumns.length > 0 ||

+ 0 - 25
packages/table/src/util.js

@@ -1,28 +1,3 @@
-let scrollBarWidth;
-
-export const getScrollBarWidth = () => {
-  if (scrollBarWidth !== undefined) return scrollBarWidth;
-
-  const outer = document.createElement('div');
-  outer.style.visibility = 'hidden';
-  outer.style.width = '100px';
-  outer.style.position = 'absolute';
-  outer.style.top = '-9999px';
-  document.body.appendChild(outer);
-
-  const widthNoScroll = outer.offsetWidth;
-  outer.style.overflow = 'scroll';
-
-  const inner = document.createElement('div');
-  inner.style.width = '100%';
-  outer.appendChild(inner);
-
-  const widthWithScroll = inner.offsetWidth;
-  outer.parentNode.removeChild(outer);
-
-  return widthNoScroll - widthWithScroll;
-};
-
 export const getCell = function(event) {
   let cell = event.target;
 

+ 5 - 0
packages/theme-default/src/common/var.css

@@ -544,4 +544,9 @@
   --------------------------*/
   --loading-spinner-size: 42px;
   --loading-fullscreen-spinner-size: 50px;
+
+  /* Scrollbar
+  --------------------------*/
+  --scrollbar-background-color: rgba(#99a9bf, .3);
+  --scrollbar-hover-background-color: rgba(#99a9bf, .5);
 }

+ 7 - 8
packages/theme-default/src/date-picker/time-spinner.css

@@ -4,22 +4,21 @@
   @b time-spinner {
     &.has-seconds {
       .el-time-spinner__wrapper {
-        width: calc(100% / 3);
+        width: 33%;
+      }
+
+      .el-time-spinner__wrapper:nth-child(2) {
+        padding-left: 1%;
       }
     }
 
     @e wrapper {
-      height: 190px;
-      overflow: hidden;
+      max-height: 190px;
+      overflow: auto;
       display: inline-block;
       width: 50%;
       vertical-align: top;
       position: relative;
-      -ms-overflow-style: none;
-
-      &:hover {
-        overflow-y: auto;
-      }
     }
 
     @e list {

+ 1 - 0
packages/theme-default/src/index.css

@@ -40,3 +40,4 @@
 @import "./rate.css";
 @import "./steps.css";
 @import "./step.css";
+@import "./scrollbar.css";

+ 66 - 0
packages/theme-default/src/scrollbar.css

@@ -0,0 +1,66 @@
+@component-namespace el {
+  @b scrollbar {
+    overflow: hidden;
+    position: relative;
+
+    &:hover,
+    &:active,
+    &:focus {
+      .el-scrollbar__bar {
+        opacity: 1;
+        transition: opacity 340ms ease-out;
+      }
+    }
+
+    @e wrap {
+      overflow: scroll;
+
+      &::-webkit-scrollbar {
+        width: 0;
+        height: 0;
+      }
+    }
+
+    @e thumb {
+      position: relative;
+      display: block;
+      size: 0;
+      cursor: pointer;
+      border-radius: inherit;
+      background-color: var(--scrollbar-background-color);
+      transition: .3s background-color;
+
+      &:hover {
+        background-color: var(--scrollbar-hover-background-color);
+      }
+    }
+
+    @e bar {
+      position: absolute;
+      right: 2px;
+      bottom: 2px;
+      z-index: 1;
+      border-radius: 4px;
+      opacity: 0;
+      transition: opacity 120ms ease-out;
+
+      @when vertical {
+        width: 6px;
+        top: 2px;
+
+        > div {
+          width: 100%;
+        }
+      }
+
+      @when horizontal {
+        height: 6px;
+        left: 2px;
+
+        > div {
+          height: 100%;
+        }
+      }
+    }
+  }
+}

+ 5 - 3
packages/theme-default/src/select-dropdown.css

@@ -43,14 +43,16 @@
     font-size: var(--select-font-size);
   }
 
+  @b select-dropdown__wrap {
+    max-height: var(--select-dropdown-max-height);
+    width: 100%;
+  }
+
   @b select-dropdown__list {
     list-style: none;
     padding: var(--select-dropdown-padding);
     margin: 0;
-    width: 100%;
-    max-height: var(--select-dropdown-max-height);
     box-sizing: border-box;
-    overflow-y: auto;
 
     @when empty {
       padding: 0;

+ 0 - 5
packages/theme-default/src/time-select.css

@@ -9,12 +9,7 @@
 
 .time-select .el-picker-panel__content {
   max-height: 200px;
-  overflow: hidden;
   margin: 0;
-
-  &:hover {
-    overflow-y: auto;
-  }
 }
 
 .time-select-item {