Обязательная пользовательская библиотека прокрутки для внешнего интерфейса - iScroll

JavaScript
Обязательная пользовательская библиотека прокрутки для внешнего интерфейса - iScroll

Автор реально ленивый, эта статья уже несколько месяцев как зародилась идея. 😊

что такое iScroll

Во многих сценариях будет очень неудобно использовать прокрутку тела, в этот раз будет использоваться частичная прокрутка элемента, и будут происходить отвратительные вещи.

  1. Уродливые полосы прокрутки появятся в браузерах на стороне ПК в Интернете и в системе Windows. (На самом деле, есть и последние стили CSS, которые можно решить, и совместимость не очень хорошая)
  2. На мобильных телефонах браузер ios не может скользить инерционно и эластично (прокрутка браузера используется по умолчанию, а не-ios системная родная прокрутка), если добавить-webkit-overflow-scrolling: touch;Используя родную прокрутку системы, совместимость не очень хорошая, и баг не один и не два 😭.
  3. Это не способствует реализации некоторых индивидуальных потребностей, таких как загрузка, обновление, подгонка и прокрутка и т. д.

Так совпало, что iScroll решает эти проблемы.

Автор iScroll — международный друг, его билет на самолет на githubздесь.

К сожалению, автор почти больше не поддерживает этот плагин iScroll, а соответствующих китайских документов в Интернете очень мало, но это не мешает нам брать этот плагин повсюду.

Давайте посмотрим, как работает iScroll

iScroll использует анимацию преобразования CSS3 для имитации эффекта инерционной и эластичной прокрутки, а эффект и производительность идеально близки к исходному эффекту прокрутки. Он также предоставляет множество функций, включая自定义滚动条,指定滚动到元素и другие функции, это также может быть легко реализовано下拉刷新,上拉加载.

Основное использование iScroll

Обязательно сначала установите его
npm install iscroll

yarn add iscroll
затем процитировать
import IScroll from 'iscroll/build/iscroll'; // 普通版
import IScroll from 'iscroll/build/iscroll-probe'; // 复杂版
import IScroll from 'iscroll/build/iscroll-infinite';

В iscroll есть несколько разных js-файлов: обычная версия, сложная версия и версия с бесконечной прокруткой. Здесь обычно используется сложная версия, которая поддерживает мониторинг позиции прокрутки в реальном времени, если мониторинг в реальном времени не требуется, можно использовать обычную версию.

Инициализировать с помощью

Вот пример фреймворка vue

<template>
  <div class="wrap">
    <div class="scroll-area">
      <div v-for="n in 50" class="item">{{ n }}</div>
    </div>
  </div>
</template>
<script>
  import IScroll from 'iscroll/build/iscroll-probe';
  
  export default {
    data() {
      scroll: null,
    },
    mounted() {
      // 提示,因为transform是对dom操作,所以需要在这个生命周期操作
      this.scroll = new IScroll('.wrap', {
        mouseWheel: true, // 允许鼠标滚轮
      });
      // 第一个参数是dom选择器,建议使用唯一性的id,这里以class为例
      // 第二个参数为参数对象,是iscroll的一些配置
      // 参数配置可以参考 http://wiki.jikexueyuan.com/project/iscroll-5/
    }
  }
</script>
<style>
  .wrap{
    height: 400px;
    overflow: hidden;
    /* 给滚动区域固定可滚动高度,并且超出隐藏 */
  }
</style>

Приведенный выше код завершает простую инициализацию и использование iscroll, вы можете увидеть эффект

效果1

обновление iScroll

Обратите внимание, что, поскольку прокручиваемый контент может быть получен и загружен асинхронно в dom, если iscroll не обновляется, это может повлиять на функцию прокрутки, поэтому при загрузке асинхронного контента необходимо вызвать метод обновления для обновления iscroll. Метод обновления следующий

<template>
  <div ref="scroll" class="wrap">
    <div class="scroll-area">
      <div v-for="n in 50" class="item">{{ n }}</div>
    </div>
  </div>
</template>
<script>
  import IScroll from 'iscroll/build/iscroll-probe';
  
  export default {
    data() {
      scroll: null,
    },
    mounted() {
      const el = this.$refs.scroll;
      this.scroll = new IScroll('.wrap', {
        ...
      });
      // ① 异步数据刷新
      getData().then(_=>{
        this.scroll.refresh();
      })
      // ② 首次滑动时刷新
      el.addEventListener('touchstart', _=>this.scroll.refresh());
    }
  }
</script>

позиция прослушивания

this.scroll = new IScroll('.wrap', {
  probeType: 3, // 滚动监听级别  有3档,3是像素级监听
});
// 用iscroll实例注册scroll事件
this.scroll.on('scroll', e => {
  // 此处不用箭头函数可以用this.x和this.y访问实时位置,用了箭头函数需要从实例上访问
  // this.scroll.x
  // this.scroll.y
})

Нечего сказать, посмотрите эффект

效果2

Обратите внимание на положительное и отрицательное значения.Значение, полученное путем мониторинга, является значением преобразования, и подтвердите направление, соответствующее положительным и отрицательным значениям.

Прокрутите до указанной позиции элемента

Здесь вам нужно использовать функцию подгонки iscroll

this.scroll = new IScroll('.wrap', {
  snap: '.item',
});
// 当设置snap属性为true时,iscroll会把容器可视区域分割为一个page
// 当设置snap属性为元素选择器时,iscroll会把对应的元素设置为一个page
// 这里我们设置为'.item'

Затем используйте IscrollgoToPageметод, перейти к соответствующему элементу

this.scroll.goToPage(0, 30, 1000);
// 参数分别为x, y, 动画时间,
// 注意x,y是传入索引,第一个是0,类推

также можно использоватьprevа такжеnextспособ пропустить предыдущий или следующий

this.scroll.prev();
this.scroll.next();

效果3

Настроить полосы прокрутки

Если вам нужна полоса прокрутки, это тоже очень просто

this.scroll = new IScroll('.wrap', {
  scrollbars: true, // 开启滚动条
  shrinkScrollbars: 'scale', // 超出滚动时,缩放滚动条
});
/* 因为iscroll的滚动条是定位实现,所以容器需要加一个相对定位 */
.wrap{
  position: relative;
}

效果4

событие щелчка

iscroll по умолчанию отключает событие клика, при необходимости его можно включить

this.scroll = new IScroll('.wrap', {
  click: true,
});

И iscroll имеет очень удобное встроенное событие касания, пока нажатие включено, оно может реагировать на касание элемента.

this.scroll = new IScroll('.wrap', {
  tap: true,
});
<template>
  <div ref="scroll" class="wrap">
    <div class="scroll-area">
      <div v-for="n in 50" class="item" @tap="onTap">{{ n }}</div>
    </div>
  </div>
</template>

iScroll липкий

В родной прокрутке, основанной на элементах dom, можно добавлять содержимое вposition: stickyдля достижения эффекта потолка.

Потолок: во время прокрутки родительского элемента, если дочерний элемент содержитposition: stickyа такжеtop: 0style, то когда содержимое прокручивается вверх, оно будет привязано к верху родительского элемента и больше не будет прокручиваться вверх. (То же самое для горизонтальной прокрутки)

никогда не играй в этоposition: stickyДа, просто попробуй и узнай. Конечно, этот css не является непобедимым, причина в том, что не хватает совместимости.кликните сюда

Хорошо, давайте поговорим о том, как iscroll реализует sticky, потому что iscroll использует преобразование для реализации прокрутки, поэтому контейнер установленoverflow: hidden, поэтому нет возможности использовать css sticky для достижения, а затем, поскольку преобразование родительского элемента прокручивается, то, когда оно достигает положения потолка, можно ли отменить преобразование дочернего элемента?

Присмотритесь к коду под ним, важно внимательно смотреть на комментарий ️

// 这段代码可以理解为是对iscroll类的扩展
// 这里的参数为iscroll类
export const extendSticky = (iScroll) => {
  let m = Math;
  // 这里是为了兼容性配置的浏览器css前缀,网络上有很多写法呢
  let vendor = (/webkit/i).test(navigator.appVersion) ? 'webkit' :
      (/firefox/i).test(navigator.userAgent) ? 'Moz' :
        'opera' in window ? 'O' : '',
    has3d = 'WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix(),
    trnOpen = 'translate' + (has3d ? '3d(' : '('),
    trnClose = has3d ? ',0)' : ')';
  
  /**
   * 这里开始拓展iscroll类
   * @param selector 需要sticky的对象集合,包含元素和sticky的位置
   * @return { iScrollStickyHeaders }
   */
   // 在iscroll原型上添加 enableStickyHeaders 方法
  iScroll.prototype.enableStickyHeaders = function (selector) {
    return new iScrollStickyHeaders(this, selector); // 拓展方法采用新的类并传参
  };

  // 参数,iscroll实例,需要sticky的元素集合
  let iScrollStickyHeaders = function (iscroll, selector) {
    if (!iscroll.options.useTransform) {
      return;
    }
    this.iscroll = iscroll;
    this.selector = selector;
    this.initialize(); // 初始化
  };
  iScrollStickyHeaders.prototype = {
    headers: [], // 存储需要sticky的对象集合
    initialize() {
      let that = this;
      this._augment();
      this.iscroll.on('refresh', function() {
        that._refresh() // 每次iscroll刷新,sticky方法也刷新
      });
      this.iscroll.refresh()
    },
    _refresh() { // 初始化或者刷新
      let elms = this.selector;
      this.headers = [ // 深拷贝对象集合
        ...elms,
      ]
      // 此处对象集合的格式为 { el: 元素, top: 需要sticky的位置 }
      // 此处可以根据习惯和喜欢自行定义格式和逻辑代码
      this._translate(0, 0); // 初始化
    },
    _augment() { // 初始化函数
      let that = this;
      this.iscroll.on('scroll', function() {
        that._translate(this.x, this.y) // iscroll滚动时,触发主函数
      });
      this.iscroll.on('beforeScrollStart', function() {
        that._translate(this.x, this.y) // iscroll即将滚动时,触发主函数
      });
      this.iscroll.on('scrollStart', function() {
        that._translate(this.x, this.y) // iscroll开始滚动时,触发主函数
      });
    },
    _translate(x, y) { // 主函数,到达sticky位置后,反向transform
      let absY = m.abs(y); // 获取y轴滚动的绝对值
      this.headers.forEach((stickyObj) => { // 遍历sticky对象
        let translateY = 0; // sticky的反向transform默认为0
        let yy = m.abs(absY - stickyObj.el.offsetTop); // 计算iscroll的y轴滚动值-当前元素距离父级的值
        // stickyObj.el.offsetTop为固定值
        // yy即为当前元素距离容器顶部的位置
        // absY < stickyObj.el.offsetTop说明该元素还没到达顶部
        // yy <= stickyObj.top 判断元素是否到达需要sticky的位置
        // ① 当元素还没到达容器顶部时,默认为0,再判断是否到达指定sticky位置
        // ② 如果没到达指定sticky,依然为0
        // ③ 如果达到指定sticky位置,那么就计算超过sticky位置后,需要反向transform的距离
        // ④ 这里默认指定位置是小于元素初始位置的,指定位置大于初始位置的,我想会很奇葩吧。
        if (absY - stickyObj.el.offsetTop > 0 || yy <= stickyObj.top) {
          // 这个公式需要反复理解一下
          // 当容器往上滚动时,容器的transform是负值,所以我们反向是正值
          // 容器向上滚动值absY不断变大,我们sticky就不断向下transform
          // stickyObj.el.offsetTop - stickyObj.top 即为容器滚动多少范围才会让元素到达指定sticky位置
          // 计算iscroll容器的滚动值 - (初始位置 - 指定位置)
          // 当滚动值等于初始位置和指定位置之差时,刚好等于0
          // 随着滚动值越来越大,超过0的部分,即为需要反向transform的值
          translateY = absY - (stickyObj.el.offsetTop - stickyObj.top);
        } else {
          translateY = 0;
        }
        // 最后拼接浏览器前缀,完成css赋值
        stickyObj.el.style[vendor + 'Transform'] = trnOpen + ('0, ' + translateY + 'px') + trnClose;
      });
    },
  };
};
export default extendSticky;

Для облегчения понимания покажу навыки работы с Axure.

效果5

Хорошо, вышеiscroll-sticky.jsИнструмент готов, давайте начнем его использовать.

<template>
  <div ref="scroll" class="wrap">
    <div class="scroll-area">
      <div v-for="n in 20" class="item">{{ n }}</div>
      <div ref="sticky" class="sticky" :top="20">21</div>
      <div v-for="n in 20" class="item">{{ n+20 }}</div>
    </div>
  </div>
</template>
<script>
  import IScroll from 'iscroll/build/iscroll-probe';
  import enableSticky from 'path/to/iscroll-sticky.js';
  enableSticky(IScroll); // 这一步是将sticky方法挂载到iscroll原型上
  
  export default {
    data() {
      scroll: null,
    },
    mounted() {
      const el = this.$refs.scroll;
      this.scroll = new IScroll('.wrap', {
        ...
      });
      const stickyEl = this.$refs.sticky;
      // 允许元素对象集合sticky
      this.scroll.enableStickyHeaders([
        {
          el: stickyEl,
          top: stickyEl.getAttribute('top') // 此处我把top值配置在了原生prop
        }
      ]);
    }
  }
</script>

Посмотрим на эффект.

效果6

надiscroll-sticky.jsЭто гибкий js, который можно настроить и изменить в соответствии с вашими потребностями.

Потяните вниз, чтобы обновить

На самом деле сам ISCrolle не имеет функции тяги к обновлению, но она может быть реализована сама по себе.

export default {
  data() {
    scroll: null,
    status: 0, // 用一个变量记录iscroll滚动状态,默认为0
    txt: '下拉刷新', // 记录刷新文本,默认
  },
  watch: {
    status() {
      // 每次iscroll的状态码变化时,就要刷新iscroll,以便iscroll重新计算dom元素
      this.iscroll.refresh();
    }
  }
}

Затем добавьте текст обновления (или анимацию)

<template>
  <div ref="scroll" class="wrap">
    <div class="scroll-area">
      <div :class="{hide: status===0}" class="refresh">{{ txt }}</div>
      <div v-for="n in 50" class="item">{{ n }}</div>
    </div>
  </div>
</template>
.refresh{
  width: 100%;
  height: 50px;
  line-height: 50px;
  text-align: center;
  &.hide{
    /* 当status为0默认时,隐藏刷新文本,通过定位到容器外面 */
    position: absolute;
    left: 0;
    top: -50px;
  }
}

Затем прослушайте раскрывающийся список isscroll.

// ...
this.scroll.on('scroll', e => {
  const y = this.iscroll.y; // 监听下拉的y值,下拉是正值
  if (y >= 50) { // 当下拉距离>=刷新文本高度时,
    this.status = 1; // 状态码变为1, 表示准备好刷新了
  }
})

В настоящее время,statusстановится 1, то мыhideОбновляемый текст стал обычной загрузкой контентаiscrollТеперь нужно четко понимать изменения dom, ключ в том, чтоОбновление iscroll после изменения состояния, наш палец не был отпущен, поэтому в настоящее время он готов к обновлению.В это время необходим новый монитор, чтобы следить за уходом пальца и остановкой прокрутки.

this.scroll.on('scroll', e => {
  const y = this.iscroll.y; // 监听下拉的y值,下拉是正值
  if (y >= 50) { // 当下拉距离>=刷新文本高度时,
    this.txt = '释放刷新';
    this.status = 1; // 状态码变为1, 表示准备好刷新了
  } else if (y > 0) { // 如果返回了,又不想刷新了,恢复status为0
	this.txt = '下拉刷新';
	this.status = 0;
  }
})
this.scroll.on('scrollEnd', e => {
  if (status === 1) { // 滚动停止时,如果是准备刷新状态
    this.txt = '刷新中。。。';
    this.status = 2; // 改变状态码,开始刷新
    this.scroll.disable(); // 刷新过程禁止滚动,这个禁用方法视需求而定。
    this.updateData(); // 假设有一个更新数据的method
  }
})
export default {
  methods: {
    updateData() {
      getData().then(_=>{
        // 数据更新完成
        this.txt = '刷新完成';
        // 延迟1秒后继续隐藏刷新文本
        setTimeout(_=>{
          this.txt = '下拉刷新';
          this.status = 0; // 状态重置为0
          this.scroll.enable();
        }, 1000);
      })
    }
  }
}

Взгляните на эффект демонстрации:

效果7

Вот демонстрация фильма «кошачий глаз», который я обычно делаю:

效果8

подтягивающая загрузка

Это также должно быть реализовано самостоятельно, но это очень просто, просто судите дно прокрутки.

this.scroll.on('scroll', e => {
  // 此处scrollEl是容器高度,contentEl是内容高度,因为y是负值,所以用scrollEl - contentEl
  if (this.scroll.y <= scrollEl.offsetHeight - contentEl.offsetHeight) {
    // do something 上拉加载
  }
});

Суммировать

iscroll — очень гибкая библиотека, которую можно свободно настроить в соответствии с желаемым эффектом.

Если вы знакомы с модульностью, вы можете попробоватьsticky,下拉刷新,上拉加载упакован в компонент.

комментарий был упомянутbetter-scroll, нет проблем, используйте то, что вам нравится.

Добро пожаловать в лайки и избранное, продолжение, связанное с iscroll, будет обновляться со временем.