Vue组件开发系列之自定义滚动Scroll组件

组件源码:
https://github.com/AntJavascript/widgetUI/tree/master/Scroll

自定义组件我主要是用来解决手机端路由返回的时候,页面会自动滚动到顶部的问题

image

组件页面结构:

<template>

    <div class="wt-scroll"  @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd">

      <div class="scroll-wrapper" v-if="direction.toLocaleUpperCase() === 'V'" :style="{transform: 'translate3d(0px, '+ distance +'px, 0px)'}">

        <slot></slot>

      </div>

      <div class="scroll-wrapper horizontal" v-else :style="{transform: 'translate3d('+ -distance +'px, 0px, 0px)'}">

        <slot></slot>

      </div>

    <div v-show="scrollbar" 

        v-if="direction.toLocaleUpperCase() === 'V'"

        class="scroll-scrollbar"

        :style="{'height':scrollHeight * 0.98 + 'px', 'top': scrollHeight * 0.01 + 'px', 'opacity':status != '' ? 1 : autoHide ? 0 : 1}">

      <div class="scroll-scrollbar-drag" :style="{'height':scrollHeight * scrollRatio + 'px', transform: 'translate3d(0px, '+ -distance * scrollRatio +'px, 0px)'}"></div>

    </div>

    <div v-show="scrollbar" v-else class="scroll-scrollbar horizontal" :style="{'opacity':status != '' ? 1 : autoHide ? 0 : 1}">

      <div class="scroll-scrollbar-drag" 

      :style="{'width':scrollWidth + 'px', transform: 'translate3d('+ distance * scrollRatio +'px, 0px, 0px)'}">

      </div>

    </div>

  </div>

</template>

代码分析:

props参数:

props: {
    scrollbar: { // 是否显示滚动条
      type: Boolean,
      default: () => {
        return false;
      }
    },
    direction: { // 组件滚动方向("V" 代表垂直滚动,"H" 代表水平滚动)
      type: String,
      default: () => {
        return 'v';
      }
    },
    autoHide: { // 滚动条是否会自动隐藏
      type: Boolean,
      default: () => {
        return false;
      }
    }
  }

data参数:

data () {
    return {
      speed: 4, // 速度
      translate: 0, // 滑动距离
      isTop: true, // 是否滑动到了顶部
      isBottom: false, // 是否滑动到了底部
      distance: 0, // 滑动距离
      hisdistance: 0, // 上一次滑动距离
      maxDistance: '', // 最大滑动距离
      start: { // 触摸坐标
        X: 0,
        Y: 0
      },
      move: { // 移动坐标
        X: 0,
        Y: 0
      },
      scrollHeight: '', // 滚动条背景高度
      scrollWidth: '', // 滚动条背景宽度
      scrollRatio: '', // 滚动条比例
      startTime: '', // 触摸开始时间
      endTime: '', // 触摸结束时间
      transtionTime: '0', // 过渡时间
      timer: '', // 定时器
      status: '' // 状态
    };
  }

touch事件:

touchStart // 手指接触屏幕触发

touchStart () {
      // 触摸坐标
      this.start.X = event.touches[0].clientX;
      this.start.Y = event.touches[0].clientY;
      this.startTime = new Date().getTime();
    }

touchmove // 滑动手指的时候触发

touchMove () {
      var self = this;
      event.preventDefault();
      self.setTranstionTime('0ms');
      // 清除定时器,不清除的话,连续滑动会导致滚动条渐隐渐现
      clearTimeout(self.timer);
      // 滑动时候的坐标
      this.move.X = event.touches[0].clientX;
      this.move.Y = event.touches[0].clientY;
      // 滑动距离
      var tance = '';
      if (this.direction.toLocaleUpperCase() === 'H') {
        tance = -(this.move.X - this.start.X); // 本次滑动距离
      } else {
        tance = this.move.Y - this.start.Y; // 本次滑动距离
      }
      // 如果当前处于顶部或者底部,就增加阻力
      if (this.hisdistance === 0 || Math.abs(this.hisdistance) === this.maxDistance) {
        tance = tance * 0.5;
      }
      this.distance = this.hisdistance + tance; // 页面滑动距离(上一次滑动距离 + 本次滑动距离)
      this.status = 'moveing'
    }

touchend // 手指离开触发

touchEnd () {
      var self = this;
      self.endTime = new Date().getTime();
      // this.move.Y 说明没有滑动
      if (this.direction.toLocaleUpperCase() === 'V') {
        if (this.move.Y === 0) {
          return;
        }
      }
      // 如果触摸时间超过500ms,则不加速滑动
      if (self.endTime - self.startTime > 500) {
        self.speed = 1;
      } else {
        self.speed = 4;
      }
      var thisTanceX = this.move.X - this.start.X; // 本次滑动是X距离
      var thisTanceY = this.move.Y - this.start.Y; // 本次滑动是Y距离
      // 设置过渡时间
      self.setTranstionTime('1000ms');
      if (this.direction.toLocaleUpperCase() === 'H') {
        // 水平滚动
        // 如果是触顶或者触底了,就把设置时间设置为300ms
        if (this.hisdistance + -thisTanceX * self.speed <= 0 || Math.abs(this.hisdistance + -thisTanceX * self.speed) >= this.maxDistance) {
          self.setTranstionTime('300ms');
        }
        this.distance = this.hisdistance + -thisTanceX * self.speed;
      } else {
        // 垂直滚动
        // 如果是触顶或者触底了,就把设置时间设置为300ms
        if (this.hisdistance + thisTanceY * self.speed >= 0 || Math.abs(this.hisdistance + thisTanceY * self.speed) >= this.maxDistance) {
          self.setTranstionTime('300ms');
        }
        this.distance = this.hisdistance + thisTanceY * self.speed;
      }
      if (this.direction.toLocaleUpperCase() === 'H') {
        // 水平滚动
        if (self.distance <= 0) {
          self.distance = 0;
          self.hisdistance = 0; // 清除上一次滑动距离
          self.isTop = true; // 滑动到了顶部
        } else if (Math.abs(self.distance) >= this.maxDistance) {
          self.isBottom = true;
          self.distance = this.maxDistance;
          self.hisdistance = self.distance; // 记录上次滑动的位置
        } else {
          self.isTop = false;
          self.isBottom = false;
          self.hisdistance = self.distance; // 记录上次滑动的位置
        }
      } else {
        // 垂直滚动
        // self.distance >= 0 说明滑动到了顶部
        if (self.distance >= 0) {
          self.distance = 0; // 滑动距离等于0
          self.hisdistance = 0; // 清除上一次滑动距离
          self.isTop = true; // 滑动到了顶部
        } else if (Math.abs(self.distance) >= this.maxDistance) {
          self.isBottom = true; // 滑动到了底部
          self.distance = -this.maxDistance; // 滑动距离等于最大滑动距离
          self.hisdistance = self.distance; // 记录上次滑动的位置
        } else {
          self.isTop = false;
          self.isBottom = false;
          self.hisdistance = self.distance; // 记录上次滑动的位置
        }
      }
      if (this.direction.toLocaleUpperCase() === 'H') {
        // 不能设置为0,否则触摸手机边界会有问题
        // self.move.X = 0;
      } else {
        self.move.Y = 0;
      }
      self.timer = setTimeout ( () => {
        this.status = '' // 清空状态
      }, ~~self.transtionTime + 300)
    }

mounted 生命周期,初始化工作

mounted () {
    var el = this.$el;
    var wrapper = el.firstChild;
    // 如果是横向滚动
    if (this.direction.toLocaleUpperCase() === 'H') {
      this.$nextTick(() => {
        var itemConutWidth = 0; // wrapper总宽度
        var len = wrapper.childElementCount; // wrapper 的子节点数量
        // 这个for循环用于计算  wrapper 所有子节点的宽度
        for (let i = 0; i < len; i++) {
          // 获取元素的marginLeft值
          var marginL= parseFloat(getComputedStyle(wrapper.children[i], false)['marginLeft'].replace('px', ''));
          // 获取元素的marginRight值
          var marginR= parseFloat(getComputedStyle(wrapper.children[i], false)['marginRight'].replace('px', ''));
          itemConutWidth+= wrapper.children[i].offsetWidth + marginL + marginR;
        }
        wrapper.style.width = itemConutWidth - el.offsetWidth + 'px'; // 设置 wrapper 的宽度
        this.maxDistance = itemConutWidth - el.offsetWidth; // 设置可滚动的最大距离
        this.scrollWidth = (el.offsetWidth / this.maxDistance) * el.offsetWidth; // 设置滚动条宽度(水平滚动时可用到)
        this.scrollRatio = (el.offsetWidth / itemConutWidth); // 设置滚动条比例
      });
      wrapper.style.display = 'flex'; // 设置display属性为 "flex"
    } else {
      /* 垂直滚动设置 */
      // 如果高度大于整个屏幕高度,则滚动区域高度等于屏幕高度
      var elHeight = el.clientHeight < MediaQuery.height ? el.clientHeight : MediaQuery.height;
      this.scrollHeight = elHeight * 0.98; // 设置滚动条高度
      this.maxDistance = wrapper.offsetHeight - elHeight; // 设置可滚动的最大距离
      this.scrollRatio = this.scrollHeight / wrapper.offsetHeight; // 设置滚动条比例
    }
  }

组件源码:
https://github.com/AntJavascript/widgetUI/tree/master/Scroll

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 本教程版权归凯旋和饥人谷所有,转载须说明来源 一 、有序列表、无序列表、自定义列表如何使用?写个简单的例子。三者在...
    凯旋阅读 576评论 0 0
  • 可持续机能 肌肉锻炼 压力
    将了个将阅读 224评论 0 0
  • 文/南歌吟 百度资料介绍: 马鲁姆火山是全球1500座活火山之一,它位于大洋洲瓦努阿图共和国的安布里姆岛上。 安布...
    南歌吟阅读 3,555评论 8 23
  • 第一次知道这本书,是大概五年前,一个偶然的机会得到这本书,但是当时并未打开它,它就这么在我电脑里躺了好多年。我看书...
    o夜雨敲窗阅读 434评论 0 1