BetterScroll: Вероятно, лучший доступный плагин для мобильной прокрутки (анализ исходного кода)

JavaScript

BetterScroll — это плагин с открытым исходным кодом, ориентированный на удовлетворение потребностей различных сценариев прокрутки на мобильной стороне.Адрес GitHub, имеет следующие функции для поддержки списка прокрутки, выпадающего обновления, всплывающего обновления, карусели, ползунка и других функций.
Чтобы выполнить эти функции, Better-Scroll обеспечивает плавную прокрутку за счет использования инерционной прокрутки, ограничивающего упругого возврата и плавного появления и исчезновения полос прокрутки. В то же время он также поддерживает множество API и событий, Конкретные поддерживаемые события можно подробно просмотреть на официальном сайте.
Поскольку он реализован на основе собственного JavaScript и не зависит ни от какой платформы, на него можно ссылаться с помощью собственного JavaScript или использовать в сочетании с текущей внешней средой MVVM.Например, пример на его официальном сайте представляет собой комбинацию с Vue. .

как использовать:

Прежде чем говорить о том, как его использовать, давайте сначала разберемся в его принципе прокрутки: при прокрутке в браузере, когда высота содержимого выше высоты внешнего контейнера, появится полоса прокрутки.Мы можем использовать полосу прокрутки чтобы увидеть лишнее.

На этом основан принцип Better-Scroll, ширина/высота контентной части должна быть больше, чем внешняя ширина/высота. Итак, используя
Когда внешнему контейнеру необходимо установить фиксированную ширину, возникает еще одна проблема, которую необходимо установить для переполнения: скрыто, это связано с тем, что лишняя часть скрыта. Затем настало время инициализировать better-scroll, что немного хлопотно, но, к счастью, автор уже инкапсулировал его под vue framework, нам просто нужно заполнить его как маггл. Но есть одно предостережение: прокручиваемый элемент может быть только первым элементом первого контейнера. Исходный код выглядит следующим образом:

  // this.scroller就是滚动的内容,this.wrapper是容器
    this.scroller = this.wrapper.children[0]

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

Основной код:

1. прокрутить до

Функция scrollTo() — это основная функция Better-Scroll, фактически мы вызываем scrollToElement.
Когда внутренней операцией по-прежнему является функция scrollTo

   BScroll.prototype.scrollTo = function (x, y, time=0, easing = ease.bounce) {
        // useTransition是否使用css3 transition,isInTransition表示是否在滚动过程中
        // this.x表示translate后的位置或者初始化this.x = 0
        this.isInTransition = this.options.useTransition
        && time > 0 && (x !== this.x || y !== this.y)

        // 如果使用的transition,就调用一系列transition的设置,默认是true
        if (!time || this.options.useTransition) {
            this._transitionProperty()
            this._transitionTimingFunction(easing.style)
            this._transitionTime(time)
            // 这个函数会更改this.x
            this._translate(x, y)

            // time存在protoType表示不仅在屏幕滑动的时候, momentum 滚动动画运行过程中实时派发 scroll 事件
            if (time && this.options.probeType === 3) {
                // 这个函数的作用是派发scroll事件
                this._startProbe()
            }

            // wheel用于picker组件设置,不用管
            if (this.options.wheel) {
                if (y > 0) {
                    this.selectedIndex = 0
                } else if (y < this.maxScrollY) {
                    this.selectedIndex = this.items.length - 1
                } else {
                    this.selectedIndex = Math.round(Math.abs(y / this.itemHeight))
                }
            } else {
                // 进行动画this._animate
                this._animate(x, y, time, easing.fn)
            }
        }
    };

Давайте рассмотрим эту функцию по порядку. Простые операции отмечены кодом, поэтому я не буду слишком много описывать. Например, this._transition — это изменение своего положения. Здесь мне нужно объяснить, когда мы делаем карту карусели, не используйте метод преобразования для создания карты карусели, потому что, когда нам нужно получить значение атрибута преобразования, значение, которое вы получите, представляет собой очень странную матрицу, получите translateX или translateY. Значение очень болезненная вещь, вы можете видеть, как автор получает значение преобразования,

matrix = matrix[style.transform].split(')')[0].split(', ')
            x = +(matrix[12] || matrix[4])
            y = +(matrix[13] || matrix[5])

Я ослеп, если вы думаете, что ваш уровень очень высок, я этого не говорил. Конфигурация probeType в this.options.probeType указывает, при каких обстоятельствах нам нужно отправить событие прокрутки.В принципе лучшей прокрутки поведение браузера по умолчанию заблокировано по умолчанию, так как же мы можем отправить событие?

  export function tap(e, eventName) {
        let ev = document.createElement('Event')
        ev.initEvent(eventName, true, true)
        e.target.dispatchEvent(ev)
    }

Создайте элемент, инициализируйте его и отправляйте события. Мы можем прослушивать такие события, как addEventListener('click', fn, false).AddEventListener(eventName, fn, false). Есть параметр easing, давайте посмотрим, что такое easing
Вот вариант смягчения:

 bounce: {
        style: 'cubic-bezier(0.165, 0.84, 0.44, 1)',
        fn: function (t) {
            return 1 - (--t * t * t * t)
        }
    }

Вы можете видеть, что смягчение выполняется с помощью функции Безье, а fn делает нашу анимацию менее жесткой. Функцию Бесселя видно, он делает анимацию менее навязчивой.

2, функция обновления

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

    BScroll.prototype.refresh = function () {
        // return getBoundingRect getRect()
        let wrapperRect = getRect(this.wrapper)
        this.wrapperWidth = wrapperRect.width
        this.wrapperHeight = wrapperRect.height

        let scrollerRect = getRect(this.scroller)
        this.scrollerWidth = scrollerRect.width
        this.scrollerHeight = scrollerRect.height
        const wheel = this.options.wheel
        // wheel用于picker组件设置
        if (wheel) {
            this.items = this.scroller.children
            this.options.itemHeight = this.itemHeight = this.items.length ? this.scrollerHeight / this.items.length : 0
            if (this.selectedIndex === undefined) {
                this.selectedIndex = wheel.selectedIndex || 0
            }
            this.options.startY = -this.selectedIndex * this.itemHeight
            this.maxScrollX = 0
            this.maxScrollY = -this.itemHeight * (this.items.length - 1)
        } else {
            // 允许滑动的距离
            this.maxScrollX = this.wrapperWidth - this.scrollerWidth
            this.maxScrollY = this.wrapperHeight - this.scrollerHeight
        }

        // 滚动原理容器的宽度小于scroller的宽度
        // scrollX设置为true表示可以横向滚动
        this.hasHorizontalScroll = this.options.scrollX && this.maxScrollX < 0
        this.hasVerticalScroll = this.options.scrollY && this.maxScrollY < 0

        // 如果水平不存在的话
        if (!this.hasHorizontalScroll) {
            this.maxScrollX = 0
            this.scrollerWidth = this.wrapperWidth
        }

        if (!this.hasVerticalScroll) {
            this.maxScrollY = 0
            this.scrollerHeight = this.wrapperHeight
        }

        this.endTime = 0
        // 移动方向
        this.directionX = 0
        this.directionY = 0
        // return el.offsetLeft
        // el.offsetLeft是距离父容器的距离
        // el.getBoundingClientRect()返回的是距离页面的距离
        this.wrapperOffset = offset(this.wrapper)

        // 切换到refresh事件
        this.trigger('refresh')

        // 重置位置
        this.resetPosition()
    }

Когда структура нашего dom меняется, нам нужно пересчитать размер родительского контейнера и контейнера, чтобы мы могли перерендерить.В этой функции нет ничего сложного для понимания.Следует отметить, что метод getBoundingClientRect() возвращает значение элемента Size и его положение относительно области просмотра. Значение, полученное с помощью getBoundingClientRect(), несколько отличается от значения, полученного с помощью element.style, которое относится к верхнему левому углу окна просмотра, а это означает, что когда получено правильное значение, на самом деле это значение left+element.clientWidth. И getBoundingClientRect() можно только читать, а element.style можно не только читать, но и получать. Расстояние от родительского контейнера, возвращаемое el.offsetLeft, если нам нужно получить расстояние элемента от документа, нам нужно написать это

export function offset(el) {
    let left = 0
    let top = 0

    while (el) {
        left -= el.offsetLeft
        top -= el.offsetTop
        el = el.offsetParent
    }

    return {
        left,
        top
    }
}

Когда обнаруживается отсутствие родительского элемента, определяется расстояние между элементом и документом.

3. триггерная функция

В исходниках better-scroll функция триггера используется много раз, посмотрим, что он сделал

 BScroll.prototype.trigger = function (type) {
        let events = this._events[type]
        if (!events) {
            return
        }

        let len = events.length
        let eventsCopy = [...events]
        for (let i = 0; i < len; i++) {
            let event = eventsCopy[i]
            let [fn, context] = event
            if (fn) {
                fn.apply(context, [].slice.call(arguments,1))
            }
        }
  }

Роль триггерной функции состоит в том, чтобы переключиться на событие, получить его и затем использовать fn для его вызова. Это не слишком сложно, вот место, которое может отразить преимущества es6, например, a = [1,2,3] В es5, если нам нужно получить длину массива a, нам нужно написать это как это

 let len = a.length

Но в es6 нам больше не нужно так писать, просто пишите так

let { length } = a

Если вам нужно получить другие значения атрибутов, вы можете заполнить их в маггловском стиле. Здесь также возникает проблема глубокого копирования, глубокое копирование массивов и объектов здесь подробно рассматриваться не будет. Я думаю, что наиболее важными из вышеперечисленных являются эти три функции

Суммировать:

Исходный код этого лучше-прокрутки хорошо организован, ведь там есть сегмент Didi D8, который очень удобен для чтения. Есть также некоторые из моих взглядов на статью об анализе исходного кода. Когда я писал эту статью об анализе исходного кода, я осознал проблему, то есть я не только сам могу это понять, но и раньше писал анализ исходного кода vuex.Теперь я чувствую, что этот метод не подходит, и правильный путь должен состоять в том, чтобы выделить важные части, и самое главное — направить идею. Публикация всего кода громоздка и не говоря уже о том, что это равносильно тому, что читатель сам читает комментарии, поэтому я думаю, что правильный путь — это придумать идею.Когда читатель пытается прочитать исходный код, он может иметь общее понятие. способный разобраться в собственных идеях
Аннотированный код загружен наGitHub
Что касается того, почему в этом заголовке не пишется анализ исходного кода для лучшей прокрутки, я боюсь, что некоторые люди скажут, что некоторые статьи по анализу исходного кода - мусор, так что по крайней мере измените его буквально (экранируйте...)