[Директива Vue] Решить проблему «проникновения прокрутки» всплывающего окна мобильного устройства.

Vue.js
[Директива Vue] Решить проблему «проникновения прокрутки» всплывающего окна мобильного устройства.

1. Описание проблемы

  • на мобильномH5страница, с которой мы часто сталкиваемся点击按钮-->弹窗-->选择选项такая сцена. Когда полоса прокрутки появляется, когда вариантов слишком много, прокрутите полосу прокрутки до нижней или верхней части контейнера. Когда вы перетаскиваете полосу прокрутки вверх или вниз, действие прокрутки проникает внутрь, а нижняя частьbodyтакже будут прокручиваться вместе.
  • Краткое изложение проблемы: когда содержимое прокручивается вверх или вниз контейнера, оно перемещается вверх или вниз.принудительно прокрутить, то происходит прокатка
  • Лучший пример: всплывающее окно с белой полосой Jingdong

白条.gif

2. Исследование решения

Ссылка много онлайн-решений, его можно разделить на три метода. После тщательного рассмотрения и анализа физических лиц приведены ниже:

использоватьjsконтролировать и изменятьcss

  1. появляется всплывающее окно
    1.1. Запишите место, где всплывающая кнопка появляется при нажатииscrollTop
    1.2. Дайтеbodyстиль{'overflow': 'hidden'}

  2. Всплывающее окно закрывается
    2.1. Аннулированиеbodyстиль{'overflow': 'hidden'}
    2.2. Дайтеbodyстиль{'top': scrollTop}

    преимущество: Простое и быстрое внедрение
    недостаток: В течение периода времени, когда всплывающее окно открывается и закрывается, если всплывающее окно не закрыто всем окном, вы увидитеbodyмерцание

использоватьjsУправление событием прокрутки по умолчанию для всплывающей области содержимого

  1. появляется всплывающее окно
    1.1 Прослушивание контейнеров контентаlayoutBoxизtouchstartа такжеtouchmoveмероприятие
    1.2. МониторингtouchstartСобытие, узнайте начальную позицию пальца, чтобы начать прокрутку области содержимогоtargetY
    1.3 МониторингtouchmoveСобытие, знать положение изменения в процессе прокрутки области содержимогоnewTargetY
    1.4. Получите расстояние, на которое контент прокручивается до верхней части контейнераscrollTop / 内容可滚动的高度 scrollHeight / 当前容器的高度 clientHeight
    1.5 Предотвращение поведения контейнеров с контентом по умолчанию при прокрутке вверх и вниз. (ключевой момент)

  2. Всплывающее окно закрывается нормально

    преимущество: Начиная с источника проблемы проникновения качения, решить проблему,jsРеализации не существуетiosПроблемы совместимости
    недостаток: проверка электронной машины, проблемы с совместимостью у отдельных брендов.

Область содержимого всплывающего окна запрещена к прокрутке, используйтеjsИмитация полос прокрутки

  1. появляется всплывающее окно
    1.1. Мониторингtouchmoveсобытие, которое предотвращает поведение по умолчанию во всем
    1.2. Мониторингtouchstartа такжеtouchmoveСобытие записывает расстояние, пройденное пальцем, используяtransform: translate3d()Атрибут для реализации прокрутки контента

  2. Всплывающее окно закрывается нормально

    преимущество:jsРеализации не существуетiosПроблемы совместимости
    недостаток:iosПружинный опыт, который потерял родную полосу прокрутки на

Второе решение, принятое арендодателем на этот раз, продолжим

3. ОбзорscrollTop / scrollHeight / clientHeight

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

  • scrollTop scrollTop

  • scrollHeight scrollHeight

  • clientHeight

clientHeight

  • Сочетание трех
图片摘自 MDN ,
链接:https://developer.mozilla.org/zh-CN/docs/Web/API/Element/clientHeight
     https://developer.mozilla.org/zh-CN/docs/Web/API/Element/scrollHeight
     https://developer.mozilla.org/zh-CN/docs/Web/API/Element/scrollTop
侵权则立即删除

В-четвертых, проблемы, с которыми столкнулись

touchmoveНевозможно предотвратить поведение по умолчанию, черезMDNНайдите информацию, чтобы найти:

На мобильных устройствах touchmove не может напрямую предотвратить поведение по умолчанию по следующим причинам:Улучшена производительность прокрутки с пассивным

Резюме:

image.png

Очки знаний:
EventTarget.addEventListener(type, listener, options)
optionsвнутреннийpassiveполе

Наша цель — заблокировать прокрутку страницы, чтоoptionsвнутреннийpassiveУстановить какfalse.

V. Предварительная реализация

написано какmixin

/**
 * @author cunhang_wei
 * @description 解决弹窗内容区滚动穿透到body的问题
 * @param $refs.layoutBox 需要事先指定 内容容器
 */

export default {
    data () {
        return {
            targetY: 0
        }
    },

    mounted () {
        if (this.$refs.layoutBox) {
            this.$el.addEventListener('touchstart', this.handleTouchstart)
            this.$el.addEventListener('touchmove', this.handleTouchmove, {
                passive: false
            })
        }

    },

    methods: {
        handleTouchstart (e) {
            this.targetY = Math.floor(e.targetTouches[0].clientY) // 手指起始触摸位置
            console.log('handleTouchstart', this.targetY)
        },
        handleTouchmove (e) {
            let layoutBox = this.$refs.layoutBox // 内容容器
            let newTargetY = Math.floor(e.targetTouches[0].clientY) // 手指滑动中触摸位置
            let sTop = layoutBox.scrollTop // 内容滚动到容器顶部的高度
            let sHeight = layoutBox.scrollHeight // 内容的可滚动高度
            let cliHeight = layoutBox.clientHeight // 当前内容容器的高度
            if (sTop <= 0 && newTargetY - this.targetY > 0 && e.cancelable) {
                console.log('下拉到页面顶部')
                e.preventDefault()
            } else if (sTop >= sHeight - cliHeight && newTargetY - this.targetY < 0 && e.cancelable) {
                console.log('上翻到页面底部')
                e.preventDefault()
            }
        }
    },

    beforeDestroy () {
        if (this.$refs.layoutBox) {
            this.$el.removeEventListener('touchstart', this.handleTouchstart)
            this.$el.removeEventListener('touchmove', this.handleTouchmove)
        }
    }
}

После написания я обнаружил, что этот файл миксина нужно вводить каждый раз, чтобы контролировать проникновение прокрутки всплывающего окна, что немного громоздко Я проверил официальную документацию Vue и нашел лучший способ, то естьглобальная директива

Шесть, оптимизация письма

написано как глобальная директиваno-through

/**
 * @author cunhang_wei
 * @description 解决弹窗内容区滚动穿透到body的问题(覆盖率90%)
 * @description 用法
 * <ul v-no-through>
 *	<li></li>
 *	<li></li>
 * </ul>
 **/

// 全局变量 targetY
var targetY = 0
export default {
    name: 'no-through',

    bind: function (el, binding) {
        // 在binding这个对象里添加属性,是为了在unbind的时候可以取到 
        binding.handleTouchstart = function (event) {
            targetY = Math.floor(event.targetTouches[0].clientY) // 手指起始触摸位置
        }
        binding.handleTouchmove = function (event) {
            let newTargetY = Math.floor(event.targetTouches[0].clientY) // 手指滑动中触摸位置
            let sTop = el.scrollTop // 内容滚动到容器顶部的高度
            let sHeight = el.scrollHeight // 内容的可滚动高度
            let cliHeight = el.clientHeight // 当前内容容器的高度
            if (sTop <= 0 && newTargetY - targetY > 0 && event.cancelable) {
                console.log('下拉到页面顶部')
                event.preventDefault()
            } else if (sTop >= sHeight - cliHeight && newTargetY - targetY < 0 && event.cancelable) {
                console.log('上翻到页面底部')
                event.preventDefault()
            }
        }
        el.addEventListener('touchstart', binding.handleTouchstart)
        el.addEventListener('touchmove', binding.handleTouchmove, {
            passive: false
        })
    },

    unbind: function (el, binding) {
        // 重置全局变量 targetY
        targetY = 0
        el.removeEventListener('touchstart', binding.handleTouchstart)
        el.removeEventListener('touchmove', binding.handleTouchmove, {
            passive: false
        })
    }
}

// 最后再去 main.js 注册为全局指令,即可使用。

Семь, реальный тест машины

  • ios тест пройден ios13
  • Телефоны Xiaomi и Redmi прошли тест на Android 10
  • OnePlus протестирован с Android 10
  • Тест мобильного телефона Huawei прошел emui11 Android 10
  • Существует проблема совместимости с Samsung S8 (предварительная оценка связана с базовой реализацией Samsung webView)

Восемь, резюме и размышление

ключ к решению проблем

  1. Знайте, при каких обстоятельствах происходит проникновение валков
  2. ЗнатьscrollTop / scrollHeight / clientHeightЭти три свойства, как их комбинировать, чтобы судить о прокрутке вверх или вниз
  3. Мобильный телефон H5touchmoveСобытия не поддерживаются по умолчаниюpreventDefault(), вам нужно вручную включить его
  4. Знания по захвату событий и всплыванию событий:

clipboard.png

Оптимизация письма

  1. мы используемVueКогда , для многократно используемой логики js рекомендуется писать ее как миксинmixins
  2. И для использованияDOMПри использовании узла рекомендуется записывать его в виде инструкцииdirectives

Наконец, учиться хорошо не плохо! Я люблю тебя, летчик, не забудь поставить палец вверх после прочтения

Последующие обновления (25.06.2021)

Недавно я просматривал github и обнаружил, что есть более совершенное решение:body-scroll-lock, может решить эту проблему очень хорошо

image.png

Пробовала локально, эффект тоже очень хороший, можно попробовать