Answer the question
In order to leave comments, you need to log in
How to improve touchmove performance in the presence of a scrollbar in Chrome on Android?
Pilyu touch interface for phones with dragging. When there is a scrollbar on the page, it touchmove
fires only a couple of times per second, resulting in terribly jerky dragging. However, the problem does not reproduce if there is no scrollbar on the page, as well as when testing in the chrome emulator and when connecting the developer console to chrome in a real android.
If added touch-events: none
to CSS, then the problem goes away, but native scrolling also stops working. Attempts to invent a bicycle in the form of JS scrolling and manually rendering the visible part of the list by Vue run into an internal Vue rendering error, which is also not yet clear what to do.
Demo:
https://codepen.io/kyrsquir/full/QWNZzav
Vue component code:
<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>
Answer the question
In order to leave comments, you need to log in
Didn't find what you were looking for?
Ask your questionAsk a Question
731 491 924 answers to any question