kyrsquir
@kyrsquir

Как повысить производительность touchmove в присутствии скроллбара в Хроме на Андроиде?

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

Если добавить touch-events: none в CSS, то проблема уходит, но перестаёт работать и нативный скроллинг. Попытки изобретения велосипеда в виде JS-скроллинга и ручного перендеринга видимой части списка силами Vue упираются во внутреннюю ошибка рендеринга Vue, с которой тоже пока непонятно что делать.

Демо:
https://codepen.io/kyrsquir/full/QWNZzav

Код компонента Vue:
<template>
    <div class="container">
        <div
            :class="containerClass"
            :style="containerStyle"
            @touchend="touchEndHandler"
            @touchmove="touchMoveHandler"
            @touchstart="touchStartHandler"
            @transitionend="transitionEndHandler"
            class="scroller-container"
        >
            <svg viewBox="0 0 100 20" always-swipeable="true" class="drag-handle">
                <polyline points="27,10 73,10" stroke-linecap="round"></polyline>
            </svg>
            <div class="scrollbox" ref="scrollbox">
                <div :key="item" class="item" v-for="item in items">
                    {{ item }}
                </div>
            </div>
        </div>
    </div>
</template>

<script>
export default {
    data() {
        return {
            deltaY: 0,
            isDraggingByHandle: false,
            isTransitioning: false,
            items: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
            offsetHeight: 0,
            scrollboxHeight: 0,
            scrollTop: 0,
            touchAction: null,
            verticalStates: [
                {
                    translateY: 0,
                },
                {
                    translateY: window.innerHeight - 200,
                },
            ],
            verticalStateIndex: 0,
        }
    },

    computed: {
        activeVerticalState() {
            return this.verticalStates[this.verticalStateIndex]
        },
        containerClass() {
            return {
                'show-scrollbar': this.isScrollable,
                transition: this.isTransitioning,
            }
        },
        containerStyle() {
            return {
                transform: `translateY(${this.translateY}px)`,
            }
        },
        isAnySwipe() {
            return this.isDraggingByHandle || !this.isScrollable
        },
        isExpanded() {
            return this.verticalStateIndex === 0
        },
        isScrollable() {
            return this.isExpanded && this.scrollHeight > this.offsetHeight
        },
        isSwipeDown() {
            return this.deltaY > 0 && (this.isAnySwipe || this.scrollTop === 0)
        },
        isSwipeUp() {
            return (
                this.deltaY < 0 &&
                (this.isAnySwipe || this.offsetHeight + this.scrollTop === this.scrollHeight)
            )
        },
        scrollbox() {
            return this.$refs.scrollbox
        },
        translateY() {
            let translateY = this.activeVerticalState.translateY
            if (this.touchAction === 'verticalSwipe' && (this.isSwipeDown || this.isSwipeUp)) {
                translateY = translateY + this.deltaY
            }
            return translateY
        },
    },

    mounted() {
        this.updateScrollboxData()
    },

    methods: {
        touchStartHandler: function({touches, target}) {
            this.updateScrollboxData()
            this.isDraggingByHandle = Boolean(target.getAttribute('always-swipeable'))
            this.touchStartCoordinates = touches[0]
            this.touchAction = 'tap'
        },
        touchMoveHandler: function(event) {
            this.updateScrollboxData()
            this.deltaY = event.touches[0].clientY - this.touchStartCoordinates.clientY
            // promote touchAction to swipe or scroll depending on deltas and other variables
            if (this.touchAction === 'tap') {
                if (this.isSwipeDown || this.isSwipeUp) {
                    this.touchAction = 'verticalSwipe'
                } else {
                    this.touchAction = 'scroll'
                }
            }
        },
        touchEndHandler: function() {
            switch (this.touchAction) {
                case 'tap':
                    if (!this.isExpanded) {
                        this.verticalStateIndex = Math.max(this.verticalStateIndex - 1, 0)
                        this.isTransitioning = true
                    }
                    break
                case 'verticalSwipe':
                    if (this.isSwipeDown) {
                        this.verticalStateIndex = Math.min(
                            this.verticalStateIndex + 1,
                            this.verticalStates.length - 1
                        )
                    } else if (this.isSwipeUp) {
                        this.verticalStateIndex = Math.max(this.verticalStateIndex - 1, 0)
                    }
                    this.isTransitioning = true
                    this.deltaY = 0
                    break
            }
        },
        transitionEndHandler() {
            this.touchAction = null
            this.isTransitioning = false
            this.updateScrollboxData()
        },
        updateScrollboxData() {
            const {scrollHeight, offsetHeight, scrollTop} = this.scrollbox
            this.offsetHeight = offsetHeight
            this.scrollHeight = scrollHeight
            this.scrollTop = scrollTop
        },
    },
}
</script>

<style lang="scss" scoped>
.container {
    display: flex;
    justify-content: center;
    margin-top: 5vh;
    overflow: hidden;

    .scroller-container {
        pointer-events: all;
        width: 90vw;
        height: 100%;

        &.transition {
            transition: transform 0.15s ease-out;
        }

        .drag-handle {
            stroke-width: 5px;
            stroke: #bfbfc0;
            width: 100%;
            height: 30px;
            background: pink;
        }

        .scrollbox {
            overflow-y: hidden;
            pointer-events: none;
            background: green;
            height: 85vh;

            .item {
                margin-bottom: 20px;
                height: 150px;
                font-size: 36px;
                background: yellow;
            }
        }

        &.show-scrollbar .scrollbox {
            overflow-y: scroll;
            pointer-events: all;
        }
    }
}
</style>
  • Вопрос задан
  • 43 просмотра
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы