基于 vue2 的响应式基础组件(滚动条),移动端和桌面端都支持

基于 vue2 的响应式基础组件(滚动条),移动端和桌面端都支持

本文章是个人开源项目 vue2do,文档地址是: https://zen0822.github.io/#/component/scroller

scroller.js

/**
 * scroller 组件 滚动条
 *
 * @prop height - 滚动区域的高度(auto | { Number }px | 100% }),
 *                auto:根据滚动内容的高度
 *                { Number }:自定义像素高度
 *                100%:根据父元素的高度
 * @prop width - 滚动区域的宽度(auto | {Number}px | 100%),同上
 * @prop autoHide - 自动隐藏滚动条
 *
 * @event scrollY - 滚动事件
 *                  return isBottom - 滚动条是否到低
 *                         isTop - 滚动条是否到顶
 *                         top - 滚动条到滚动区域的顶部的当前距离
 *                         offset - 滚动条离滚动区域的顶部的距离
 * @event scrollX - 滚动事件
 *                  return isRight - 滚动条是否到结束的地方
 *                         isLeft - 滚动条是否到开始的地方
 *                         left - 滚动条到滚动区域的最左边的当前距离
 *                         offset - 滚动条离滚动区域的顶部的距离
 * @event yBarChange - y-bar 滚动条改变
 *                  return isBottom - 滚动条是否到低
 *                         isTop - 滚动条是否到顶
 *                         top - 滚动条到滚动区域的顶部的当前距离
 *                         offset - 滚动条离滚动区域的顶部的距离
 * @event xBarChange - x-bar 滚动条改变
 *                  return isRight - 滚动条是否到结束的地方
 *                         isLeft - 滚动条是否到开始的地方
 *                         left - 滚动条到滚动区域的最左边的当前距离
 *                         offset - 滚动条离滚动区域的顶部的距离
 * @event scrollerChange - 滚动区域的高度/宽度变化
 */

import './Scroller.scss'

import baseMixin from '../../mixin/base'
import apiMixin from './Scroller.api'
import render from './Scroller.render.js'

import {
  offset as propOffset
} from '../../util/dom/prop'

import MotionFade from '../MotionFade/MotionFade'

// 滚动一次的滚动区域走的像素大小
const SCROLL_PIXEL = 10

export default {
  name: 'Scroller',

  mixins: [baseMixin, apiMixin],

  render,

  components: {
    'motion-fade': MotionFade
  },

  props: {
    height: {
      type: [Number, String],
      default: 'auto',
      validator(val) {
        if (typeof val === 'number') {
          return true
        } else if (val === 'auto' || val === '100%') {
          return true
        } else {
          return false
        }
      }
    },

    width: {
      type: [Number, String],
      default: 'auto',
      validator(val) {
        if (typeof val === 'number') {
          return true
        } else if (val === 'auto' || val === '100%') {
          return true
        } else {
          return false
        }
      }
    },

    autoHide: {
      type: Boolean,
      default: false
    }
  },

  data() {
    this.compName = 'scroller' // 组件名字
    this.interValInitScroller = '' // 初始化滚动条定时器

    return {
      yData: { // y-scroller detail
        barAndScrollerOffset: 0, // 滚动条和滚动区域的偏移值
        barLength: 0, // 滚动条的高度
        barTop: 0, // bar 的高度
        boxBarRate: 0, // 滚动内容 / 滚动条区域
        boxAndScrollerOffset: 0, // 滚动内容和滚动区域的偏移值
        isMousedown: false, // 滚动条的 mousedown 事件
        oldBarTop: 0, // 记录上一次滚动条的高度
        scrollBarPixel: 0, // 滚动一次的滚动条走的像素大小
        scrollerContainBox: false // 滚动条的高度是否大于滚动区域
      },

      xData: { // x-scroller detail
        barLength: 0,
        barLeft: 0,
        barAndScrollerOffset: 0,
        boxBarRate: 0,
        boxAndScrollerOffset: 0,
        isMousedown: false,
        oldBarLeft: 0,
        scrollBarPixel: 0,
        scrollerContainBox: false
      },

      boxTop: 0, // box 离最顶端的偏移值
      boxLeft: 0, // box 离最开始的偏移值
      boxHeight: 0, // 滚动内容的高度
      boxWidth: 0, // 滚动内容的宽度
      boxStyleHeight: 0, // 滚动内容的样式高度
      boxStyleWidth: 0, // 滚动内容的样式宽度
      scrollerHeight: 0, // 滚动区域的高度
      scrollerWidth: 0, // 滚动区域的宽度
      parentHeight: 0, // 滚动区域的父元素的高度
      parentWidth: 0, // 滚动区域的父元素的宽度
      showBar: false, // 滚动条自动隐藏的状态
      isTouchStart: false, // 滚动区域的 touchend 事件
      scrolling: false, // 记录连续滚动的标注
      moving: false, // 记录是否还在触摸移动中
      hasScrollerGrandpa: false, // 是否有 scroller 组件的祖先

      touchStart: { // 记录开始触摸滚动区域的坐标
        x: 0,
        y: 0
      },

      pointStart: { // 记录开始点击滚动条的坐标
        x: 0,
        y: 0
      }
    }
  },

  computed: {
    boxStyle() {
      return {
        top: this.boxTop + 'px',
        left: this.boxLeft + 'px'
      }
    },
    scrollerStyle() {
      return {
        height: this.scrollerHeight + 'px',
        width: this.scrollerWidth + 'px'
      }
    },
    xComputed() { // x 方向的计算属性
      return {
        barDisplay: !this.xData.scrollerContainBox && (!this.autoHide || this.showBar),
        isLeft: this.xData.barLeft === 0,
        isRight: this.xData.barLeft === this.xData.barAndScrollerOffset,
        barStyle: {
          'width': this.xData.barLength + 'px',
          'left': this.xData.barLeft + 'px'
        }
      }
    },
    yComputed() { // y 方向的计算属性
      return {
        // 是否显示滚动条
        barDisplay: !this.yData.scrollerContainBox && (!this.autoHide || this.showBar),
        // 滚动条是否在顶部
        isTop: this.yData.scrollerContainBox || this.yData.barTop === 0,
        // 滚动条是否在底部
        isBottom: this.yData.scrollerContainBox || this.yData.barTop === this.yData.barAndScrollerOffset,
        barStyle: {
          'height': this.yData.barLength + 'px',
          'top': this.yData.barTop + 'px'
        }
      }
    },
    cPrefix() { // 组件类名的前缀
      return `${this.compPrefix}-scroller`
    }
  },

  watch: {
    barTop(val) {
      this.triggerScroll('y')
    },
    barLeft(val) {
      this.triggerScroll('x')
    },
    'yComputed.barDisplay' (val) {
      val ? this.$refs.bar.enter() : this.$refs.bar.leave()
    }
  },

  methods: {
    _initComp() {
      this.$box = this.$refs.box
      this._initScroller()

      this.interValInitScroller = setInterval(() => {
        this._initScroller()
      }, 50)
    },

    _binder() {
      document.addEventListener('mousemove', this.scrollerMouseMove)
      document.addEventListener('mouseup', this.scrollerMouseUp)
    },

    /**
     * '1.435px' => 1.44
     */
    _getNumFromStr(str) {
      return Math.round(parseFloat(str))
    },

    // 初始化滚动条
    _initScroller() {
      const $elParent = this.$el.parentElement
      const parentStyle = getComputedStyle($elParent)

      const parentH = parentStyle.height
      const parentW = parentStyle.width

      // 根据父元素的高宽都等于 auto 可以断言出元素的祖父元素有可能是隐藏的
      if (parentW === 'auto' && parentH === 'auto') {
        return false
      }

      // TODO: 如果 box 元素里面时绝对定位的元素则不能这样判断
      // 之后会优化根据 scroller 的父元素来重新定义 box 的元素的高度和宽度
      // 现在先让 box 的高度和宽度变成默认值来测量元素的宽度
      Object.assign(this.$box.style, {
        height: 'auto',
        width: 'auto'
      })

      let boxHeight = this.$box.offsetHeight
      let boxWidth = this.$box.offsetWidth

      const yData = this._initScrollerData({
        length: this.height,
        boxLength: boxHeight,
        type: 'y'
      })

      const xData = this._initScrollerData({
        length: this.width,
        boxLength: boxWidth,
        type: 'x'
      })

      let scrollerHeight = yData.scrollerLength
      let scrollerWidth = xData.scrollerLength

      boxHeight = yData.boxLength !== -1 ? yData.boxLength : boxHeight
      boxWidth = xData.boxLength !== -1 ? xData.boxLength : boxWidth

      const scrollerHeightChanged = scrollerHeight !== this.scrollerHeight
      const scrollerWidthChanged = scrollerWidth !== this.scrollerWidth

      const boxHeightChanged = boxHeight !== this.boxHeight
      const boxWidthChanged = boxWidth !== this.boxWidth

      if (scrollerHeightChanged || scrollerWidthChanged) {
        this._scrollerChange()
      }

      if (scrollerHeightChanged || boxHeightChanged) {
        this._initScrollBar({
          type: 'y',
          scrollerLength: yData.scrollerLength,
          scrollerContainBox: yData.scrollerContainBox,
          boxLength: boxHeight
        })
      }

      if (scrollerWidthChanged || boxWidthChanged) {
        this._initScrollBar({
          type: 'x',
          scrollerLength: xData.scrollerLength,
          scrollerContainBox: xData.scrollerContainBox,
          boxLength: boxWidth
        })
      }

      this.scrollerHeight = yData.scrollerLength
      this.scrollerWidth = xData.scrollerLength

      this.boxHeight = yData.boxLength !== -1 ? yData.boxLength : boxHeight
      this.boxWidth = xData.boxLength !== -1 ? xData.boxLength : boxWidth

      Object.assign(this.$el.style, {
        height: `${this.scrollerHeight}px`,
        width: `${this.scrollerWidth}px`
      })
      Object.assign(this.$box.style, {
        height: `${this.boxHeight}px`,
        width: `${this.boxWidth}px`
      })
    },

    /**
     * 初始化滚动区域的数据
     * @param { Object } - 选项数据
     *                   type - 滚动条类型
     *                   parentLength - 滚动的 高度/宽度
     *                   boxLength - 滚动内容的 高度/宽度
     *                   length - 指定的滚动区域的 高度/宽度
     */
    _initScrollerData({
      type,
      boxLength,
      length
    }) {
      const $el = this.$el
      let scrollerContainBox = false // 滚动区域是否大过滚动内容
      let barPositionName = `bar${type === 'y' ? 'Top' : 'Left'}` // 滚动条位置名字
      let boxPositionName = `box${type === 'y' ? 'Top' : 'Left'}` // 滚动内容位置名字
      let scrollerLength = 0 // 滚动区域的高度/宽度
      let boxL = -1 // 需要重新赋值给 box 的 高度/宽度 像素

      if (length === '100%') {
        // TODO:优化计算次数

        let lengthType = type === 'y' ? 'height' : 'width'
        let offsetLengthType = type === 'y' ? 'offsetHeight' : 'offsetWidth'
        let parentLength = 0 // 滚动区域的父元素减去除自身的子元素的高度/宽度

        // 滚动区域的 宽度/高度 需要被滚动内容撑大,
        // 并且需要检查父元素的 宽度/高度 之后,
        // 才能正确断言滚动区域的 宽度/高度
        $el.style[lengthType] = boxLength + 'px'
        const $elOffset = propOffset($el)
        const $elParent = $el.parentElement
        const $elParentChildren = Array.from($elParent.children)
        const parentStyle = getComputedStyle($elParent)
        const parentL = $elParent[offsetLengthType]

        let otherChildrenLength = 0
        let paddingLength = 0
        let borderLength = 0

        // 计算 border 和 padding 宽度/高度
        if (type === 'y') {
          paddingLength = this._getNumFromStr(parentStyle.paddingTop) + this._getNumFromStr(parentStyle.paddingBottom)
          borderLength = this._getNumFromStr(parentStyle.borderTopWidth) + this._getNumFromStr(parentStyle.borderBottomWidth)
        } else {
          paddingLength = this._getNumFromStr(parentStyle.paddingLeft) + this._getNumFromStr(parentStyle.paddingRight)
          borderLength = this._getNumFromStr(parentStyle.borderLeftWidth) + this._getNumFromStr(parentStyle.borderRightWidth)
        }

        // 计算是否有子元素的高度/宽度是撑大了父元素
        $elParentChildren.forEach((item) => {
          const itemStyle = getComputedStyle(item)
          const itemStyleDisplay = itemStyle.display
          const itemStylePosition = itemStyle.position
          const itemOffset = propOffset(item)

          // 脱离文档流的不计算(relative 例外)
          // 脱离文本流的不计算
          // 等于自身元素的不计算
          // 隐藏的不计算
          if (item === $el ||
            itemStyle.float !== 'none' ||
            itemStyleDisplay === 'none' ||
            (itemStylePosition !== 'static' && itemStylePosition !== 'relative')) {
            return false
          }

          // 当计算宽度的时候,子元素在组件的左右两边不计算
          // 当计算高度的时候,子元素在组件的上下两边不计算
          if (type === 'x') {
            if (itemStyleDisplay !== 'inline-block') {
              return false
            }

            if (itemOffset.top > $elOffset.top + boxLength || $elOffset.top > itemOffset.top + item.offsetHeight) {
              return false
            }
          } else {
            if (itemOffset.left > $elOffset.left + boxLength || $elOffset.left > itemOffset.left + item.offsetWidth) {
              return false
            }
          }

          otherChildrenLength += item[offsetLengthType]
        })

        // 减去 border、 padding 和其他子元素的宽度/高度
        parentLength = parentL - paddingLength - borderLength - otherChildrenLength

        // 父元素大于滚动内容
        if (parentLength >= boxLength) {
          boxL = parentLength
          scrollerLength = parentLength
        } else {
          scrollerLength = parentLength
        }

        scrollerContainBox = parentLength >= boxLength
      } else if (length === 'auto') {
        scrollerContainBox = true
        scrollerLength = boxLength
      } else {
        scrollerContainBox = length >= boxLength
        scrollerLength = scrollerContainBox ? boxLength : length
      }

      if (scrollerContainBox) {
        this[boxPositionName] = 0
        this[barPositionName] = 0
      }

      return {
        scrollerLength,
        scrollerContainBox,
        boxLength: boxL
      }
    },

    /**
     * 初始化滚动条的数据
     * @param { Object } - 选项数据
     *                   type - 滚动条类型
     *                   boxLength - 滚动内容的高度/宽度
     *                   scrollerLength - 滚动区域的高度/宽度
     *                   scrollerContainBox - 滚动区域是否大过滚动内容
     */
    _initScrollBar({
      type,
      boxLength,
      scrollerLength,
      scrollerContainBox
    }) {
      let barName = type + 'Data' // 滚动条数据的名字
      let boxBarRate = 0 // 滚动内容和滚动条的比
      let barLength = 0 // 滚动条的长度
      let boxAndScrollerOffset = 0 // 滚动内容和滚动区域的偏移值
      let barAndScrollerOffset = 0 // 滚动条和滚动区域的偏移值
      let barPositionName = `bar${type === 'y' ? 'Top' : 'Left'}` // 滚动条位置名字
      let boxPositionName = `box${type === 'y' ? 'Top' : 'Left'}` // 滚动内容位置名字

      boxBarRate = boxLength / scrollerLength
      barLength = scrollerLength / boxBarRate

      boxAndScrollerOffset = boxLength - scrollerLength
      barAndScrollerOffset = scrollerLength - barLength

      this[barName].scrollerContainBox = scrollerContainBox
      this[barName].boxBarRate = boxBarRate
      this[barName].barLength = barLength
      this[barName].scrollBarPixel = SCROLL_PIXEL / boxBarRate
      this[barName].boxAndScrollerOffset = boxAndScrollerOffset
      this[barName].barAndScrollerOffset = barAndScrollerOffset

      this._boxAndBarScroll({
        type: 'y',
        boxDistance: 0,
        barDistance: 0
      })
      this._boxAndBarScroll({
        type: 'x',
        boxDistance: 0,
        barDistance: 0
      })

      this.triggerChangeBar(type)
    },

    /**
     * 滚动条和滚动区域的滚动操作的相关数据
     * @param { Object } - 选项数据
     *                     type - 滚动条类型
     *                     barDistance - 滚动条的位移
     *                     boxDistance - 滚动内容的位移
     */
    _boxAndBarScroll({
      type,
      boxDistance,
      barDistance
    }) {
      let barName = type + 'Data'
      let barPositionName = `bar${type === 'y' ? 'Top' : 'Left'}`
      let boxPositionName = `box${type === 'y' ? 'Top' : 'Left'}`

      let barAndScrollerOffset = this[barName].barAndScrollerOffset
      let boxAndScrollerOffset = this[barName].boxAndScrollerOffset

      // 调整内容区域和滚动条的位置
      this[boxPositionName] = this[boxPositionName] < -boxAndScrollerOffset ? -boxAndScrollerOffset : this[boxPositionName]
      this[barName][barPositionName] = this[barName].scrollerContainBox ? 0 : -this[boxPositionName] * barAndScrollerOffset / boxAndScrollerOffset

      let boxPosition = this[boxPositionName] + boxDistance
      let barPosition = this[barName][barPositionName] + barDistance

      if (boxDistance >= 0) {
        this[barName][barPositionName] = barPosition < 0 ? 0 : barPosition
        this[boxPositionName] = boxPosition > 0 ? 0 : boxPosition
      } else {
        this[barName][barPositionName] = barPosition > barAndScrollerOffset ? barAndScrollerOffset : barPosition
        this[boxPositionName] = boxPosition < -boxAndScrollerOffset ? -boxAndScrollerOffset : boxPosition
      }
    },

    /**
     * 滚动条的高度/宽度改变事件
     */
    _scrollerChange() {
      return this.$nextTick(() => {
        this.$emit('scrollerChange', {
          emitter: this,
          scrollerWidth: this.scrollerWidth,
          scrollerHeight: this.scrollerHeight
        })
      })
    }
  },

  created() {
    function checkScrollerParent(parent = {}) {
      if (parent.compName === 'scroller') {
        return true
      } else if (parent.constructor.name === 'VueComponent') {
        return checkScrollerParent(parent.$parent)
      } else {
        return false
      }
    }

    this.hasScrollerGrandpa = checkScrollerParent(this.$parent)
  },

  destroyed() {
    clearInterval(this.interValInitScroller)
  }
}

scroller.render.js

export default function (h) {
  return h(
    'div', {
      class: [this.cPrefix],
      on: {
        mouseenter: this.scrollerMouseenter,
        mouseleave: this.scrollerMouseleave,
        wheel: this.mouseWheel,
        touchstart: this.scrollerTouchStart,
        touchmove: this.scrollerTouchMove,
        touchend: this.scrollerTouchEnd
      }
    }, [
      h('div', {
        class: [this.xclass('box')],
        style: this.boxStyle,
        ref: 'box'
      }, this.$slots.default),

      h('motion-fade', {
        props: {
          opacity: true,
          speed: 'fast',
          display: !this.autoHide
        },
        ref: 'bar'
      }, [
        h('div', {
          class: [this.xclass(['bar', 'y-bar'])],
          on: {
            click: this.barClick,
            mousedown: this.yBarMouseDown
          },
          style: this.yComputed.barStyle
        })
      ]),

      h('div', {
        class: [this.xclass(['bar', 'x-bar'])],
        on: {
          click: this.barClick,
          mousedown: this.xBarMouseDown
        },
        style: this.xComputed.barStyle,
        ref: 'xBar',
        directives: [{
          name: 'show',
          value: this.xComputed.barDisplay
        }]
      })
    ]
  )
}

scroller.api.js

/**
 * scroller.api
 */

// 滚动一次的滚动区域走的像素大小
const SCROLL_PIXEL = 10

export default {
  methods: {
    initScroller() {
      return this._initScroller()
    },

    barClick(evt) {
      evt.preventDefault()
      evt.stopPropagation()
    },

    yBarMouseDown(evt) {
      this.yData.isMousedown = true

      this.pointStart = {
        x: event.clientX,
        y: event.clientY
      }
    },

    xBarMouseDown(evt) {
      this.xData.isMousedown = true

      this.pointStart = {
        x: event.clientX,
        y: event.clientY
      }
    },

    scrollerMouseMove(evt) {
      if (!this.yData.isMousedown && !this.xData.isMousedown) {
        return false
      }

      evt.preventDefault()

      let type = this.yData.isMousedown ? 'y' : 'x'
      let distance = evt[`client${type.toUpperCase()}`] - this.pointStart[type]

      this._boxAndBarScroll({
        type,
        boxDistance: -distance * this[`${type}Data`].boxBarRate,
        barDistance: distance
      })

      this.pointStart = {
        x: evt.clientX,
        y: evt.clientY
      }

      return this.triggerScroll(type)
    },

    scrollerMouseUp(evt) {
      evt.preventDefault()

      this.yData.isMousedown = false
      this.xData.isMousedown = false
    },

    scrollerMouseenter(evt) {
      this.showBar = true
    },

    scrollerMouseleave(evt) {
      this.showBar = false
    },

    mouseWheel(evt) {
      this.triggerScroll('y')

      if ((evt.deltaY < 0 && this.yComputed.isTop === 0) ||
        (evt.deltaY > 0 && this.yComputed.isBottom === 0)) {
        return false
      }

      let barTop = 0
      let boxTop = 0

      this.yData.oldBarTop = this.yData.barTop

      this._boxAndBarScroll({
        type: 'y',
        boxDistance: evt.deltaY > 0 ? -SCROLL_PIXEL : SCROLL_PIXEL,
        barDistance: evt.deltaY > 0 ? this.yData.scrollBarPixel : -this.yData.scrollBarPixel
      })

      if (this.yComputed.isBottom || this.yComputed.isTop) {
        if (this.scrolling) {
          evt.preventDefault()

          return false
        }

        this.scrolling = true

        setTimeout(() => {
          this.scrolling = false
        }, 200)
      }

      if (!(this.yComputed.isBottom || this.yComputed.isTop) || this.yData.oldBarTop !== this.yData.barTop) {
        evt.preventDefault()
      }
    },

    scrollerTouchStart(evt) {
      this.isTouchStart = true
      this.showBar = true

      this.touchStart = {
        x: evt.touches[0].clientX,
        y: evt.touches[0].clientY
      }
    },

    scrollerTouchMove(evt) {
      if (this.yData.scrollerContainBox && this.xData.scrollerContainBox) {
        this.triggerScroll('y')

        return false
      }

      this.showBar = true

      if (!this.isTouchStart) {
        return false
      }

      let yDistance = this.touchStart.y - evt.touches[0].clientY
      let xDistance = this.touchStart.x - evt.touches[0].clientX

      if (!this.yData.scrollerContainBox) {
        this._boxAndBarScroll({
          type: 'y',
          boxDistance: -yDistance,
          barDistance: yDistance / this.yData.boxBarRate
        })

        this.triggerScroll('y')
      }

      if (!this.xData.scrollerContainBox) {
        this._boxAndBarScroll({
          type: 'x',
          boxDistance: -xDistance,
          barDistance: xDistance / this.xData.boxBarRate
        })


        this.triggerScroll('x')
      }

      this.touchStart = {
        x: evt.touches[0].clientX,
        y: evt.touches[0].clientY
      }

      // 滚动区域正方向移动
      // TODO: 优化,可以在滚动到底部得时候触发父容器得滚动事件
      if (yDistance > 0) {
        if (!this.yComputed.isBottom || this.hasScrollerGrandpa) {
          evt.preventDefault()
        }
      } else {
        if (!this.yComputed.isTop || this.hasScrollerGrandpa) {
          evt.preventDefault()
        }
      }
    },

    scrollerTouchEnd(evt) {
      this.showBar = false
      this.isTouchStart = false
      this.moving = false
    },

    /**
     * 触发滚动条滚动事件
     */
    triggerScroll(type) {
      let data = {}
      let eventName = ''

      if (type === 'y') {
        eventName = 'scrollY'
        data = {
          emitter: this,
          top: this.yData.barTop,
          offset: this.yData.barAndScrollerOffset,
          isBottom: this.yComputed.isBottom,
          isTop: this.yComputed.isTop
        }
      } else {
        eventName = 'scrollX'
        data = {
          emitter: this,
          left: this.xData.barLeft,
          offset: this.xData.barAndScrollerOffset,
          isRight: this.xComputed.isRight,
          isLeft: this.xComputed.isLeft
        }
      }

      return this.$nextTick(() => {
        this.$emit(eventName, data)
      })
    },

    triggerChangeBar(type) {
      let data = {}
      let eventName = ''

      if (type === 'y') {
        eventName = 'yBarChange'
        data = {
          isBottom: this.yComputed.isBottom,
          isTop: this.yComputed.isTop,
          boxWidth: this.boxWidth,
          boxHeight: this.boxHeight,
          hasScroller: !this.yData.scrollerContainBox
        }
      } else {
        eventName = 'xBarChange'
        data = {
          isLeft: this.xComputed.isLeft,
          isRight: this.xComputed.isRight,
          boxWidth: this.boxWidth,
          boxHeight: this.boxHeight,
          hasScroller: !this.xData.scrollerContainBox
        }
      }

      return this.$emit(eventName, data)
    }
  }
}

scroller.scss

@import "../../scss/config.scss";
@import "../../scss/extend.scss";
@import "../../scss/name.scss";

/**
 * scroller 组件样式
 */

$C_PREFIX: #{ $COMP_SCROLLER };

.#{$C_PREFIX} {
  overflow: hidden;
  position: relative;

  .#{$C_PREFIX}-box {
    position: absolute;
    top: 0;
    left: 0;
  }

  .#{$C_PREFIX}-bar {
    position: absolute;
    border-radius: 4px;
    background-color: $color-grey;
    opacity: .6;
    transition: opacity 150ms ease-out;

    z-index: 1;

    &:hover{
      opacity: .8;
    }

    &.#{$C_PREFIX}-x-bar {
      bottom: 0;
      height: 5px;
    }

    &.#{$C_PREFIX}-y-bar {
      right: 0;
      width: 5px;
    }
  }
}

你可能感兴趣的:(javascript,vue-js)