Javascript 指针事件API和拖动
我正在尝试使用实现仅限移动的触摸UI,但这不起作用: 目标是允许在同一UI中滚动卡片并在展开和折叠状态之间拖动卡片。在Chrome emulator和iOS Safari中,我只能通过拖动手柄来工作。通过卡片内容拖动在任何地方都不起作用 在Android上,拖动Chrome根本不起作用 最奇怪的是,使用Javascript 指针事件API和拖动,javascript,vue.js,google-chrome,drag,touch-event,Javascript,Vue.js,Google Chrome,Drag,Touch Event,我正在尝试使用实现仅限移动的触摸UI,但这不起作用: 目标是允许在同一UI中滚动卡片并在展开和折叠状态之间拖动卡片。在Chrome emulator和iOS Safari中,我只能通过拖动手柄来工作。通过卡片内容拖动在任何地方都不起作用 在Android上,拖动Chrome根本不起作用 最奇怪的是,使用touchstart、touchmove和touchmendevents(),同一个用户界面无处不在,但在Android上的Chrome上,在滚动条的存在下,touchmove的性能是如此糟糕,
touchstart
、touchmove
和touchmend
events(),同一个用户界面无处不在,但在Android上的Chrome上,在滚动条的存在下,touchmove
的性能是如此糟糕,以至于我试图用指针事件来重新实现它
Vue组件代码:
<template>
<div class="container">
<div
:class="containerClass"
:style="containerStyle"
@pointerdown="pointerDownHandler"
@pointermove="pointerMoveHandler"
@pointerup="pointerUpHandler"
@pointerover="reportEvent"
@pointerenter="reportEvent"
@pointercancel="reportEvent"
@pointerout="reportEvent"
@pointerleave="reportEvent"
@gotpointercapture="reportEvent"
@lostpointercapture="reportEvent"
@transitionend="transitionEndHandler"
class="card"
>
<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)`,
};
},
isAnySwipeAllowed() {
return this.isDraggingByHandle || !this.isScrollable;
},
isExpanded() {
return this.verticalStateIndex === 0;
},
isScrollable() {
return this.isExpanded && this.scrollHeight > this.offsetHeight;
},
isSwipeDown() {
return (
this.deltaY > 0 && (this.isAnySwipeAllowed || this.scrollTop === 0)
);
},
isSwipeUp() {
return (
this.deltaY < 0 &&
(this.isAnySwipeAllowed ||
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: {
pointerDownHandler: function ({ clientY, target, pointerId }) {
console.log("pointerdown", target, pointerId);
target.setPointerCapture(pointerId);
this.updateScrollboxData();
this.isDraggingByHandle = Boolean(
target.getAttribute("always-swipeable")
);
this.touchStartClientY = clientY;
this.touchAction = "tap";
},
pointerMoveHandler: function ({ clientY, target, pointerId }) {
console.log("pointermove", target, pointerId, this.deltaY);
this.deltaY = clientY - this.touchStartClientY;
// 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";
this.updateScrollboxData();
}
}
},
pointerUpHandler: function ({ target, pointerId }) {
console.log("pointerup", target, pointerId);
target.releasePointerCapture(pointerId);
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;
}
},
reportEvent({ type, target, pointerId }) {
console.log(type, target, pointerId);
},
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;
touch-action: none;
.card {
pointer-events: all;
width: 90vw;
height: 100%;
touch-action: none;
&.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>
{{item}}
导出默认值{
数据(){
返回{
德尔泰:0,
IsDragingByHandle:false,
isTransitioning:false,
项目:[
1.
2.
3.
4.
5.
6.
7.
8.
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
],
离地:0,
scrollboxHeight:0,
滚动顶端:0,
touchAction:null,
垂直状态:[
{
translateY:0,
},
{
translateY:window.innerHeight-200,
},
],
垂直状态索引:0,
};
},
计算:{
activeVerticalState(){
返回this.verticalStates[this.verticalStateIndex];
},
集装箱类(){
返回{
“显示滚动条”:this.iscrowllable,
转变:这是转变,
};
},
集装箱运输方式(){
返回{
transform:`translateY(${this.translateY}px)`,
};
},
IsAnysis(允许){
返回此.isDragingByHandle | | |!此.isCrollable;
},
isExpanded(){
返回此值。verticalStateIndex==0;
},
可转换的{
返回this.isExpanded&&this.scrollHeight>this.offsetHeight;
},
IsSweepDown(){
返回(
this.deltaY>0&(this.isAnySwipeAllowed | | this.scrollTop==0)
);
},
Isswipup(){
返回(
这是德尔泰<0&&
(这是允许的||
this.offsetHeight+this.scrollTop==this.scrollHeight)
);
},
滚动框(){
返回此。$refs.scrollbox;
},
translateY(){
让translateY=this.activeVerticalState.translateY;
如果(
this.touchAction==“垂直滑动”&&
(this.isSwipeDown | this.isSwipeUp)
) {
translateY=translateY+this.deltaY;
}
返回translateY;
},
},
安装的(){
this.updateScrollboxData();
},
方法:{
pointerDownHandler:函数({clientY,target,pointerId}){
log(“pointerdown”、target、pointerId);
target.setPointerCapture(指针ID);
this.updateScrollboxData();
this.isDragingByHandle=布尔值(
target.getAttribute(“始终可切换”)
);
this.touchStartClientY=clientY;
this.touchAction=“点击”;
},
pointerMoveHandler:函数({clientY,target,pointerId}){
log(“pointermove”,target,pointerId,this.deltaY);
this.deltaY=clientY-this.touchStartClientY;
//根据增量和其他变量,将touchAction升级为滑动或滚动
如果(this.touchAction==“点击”){
如果(this.isSwipeDown | this.isSwipeUp){
this.touchAction=“垂直滑动”;
}否则{
this.touchAction=“滚动”;
this.updateScrollboxData();
}
}
},
pointerUpHandler:函数({target,pointerId}){
log(“pointerup”、target、pointerId);
target.releasePointerCapture(指针ID);
开关(此.touchAction){
案例“tap”:
如果(!this.isExpanded){
this.verticalStateIndex=Math.max(this.verticalStateIndex-1,0);
this.isTransitioning=true;
}
打破
案例“垂直滑动”:
如果(此.isSwipeDown){
this.verticalStateIndex=Math.min(
此.verticalStateIndex+1,
此.verticalStates.length-1
);
}否则,如果(此.isswipup){
this.verticalStateIndex=Math.max(this.verticalStateIndex-1,0);
}
this.isTransitioning=true;
此值为0.deltaY=0;
打破
}
},
reportEvent({type,target,pointerId}){
日志(类型、目标、指针ID);
},
transitionEndHandler(){
this.touchAction=null;
this.isTransitioning=false;
this.updateScrollboxData();
},
updateScrollboxData(){
const{scrollHeight,offsetHeight,scrollTop}=this.scrollbox;
this.offsetHeight=offsetHeight;
this.scrollHeight=滚动高度;
this.scrollTop=scrollTop;
},
},
};
.集装箱{
显示器:flex;
证明内容:中心;
边缘顶部:5vh;
溢出:隐藏;
触摸动作:无;
.卡片{
指针事件:全部;
宽度:90vw;
身高:100%;
触摸动作:无;
&.过渡{
转换:转换0.15秒缓解;
}
.拖动手柄{
笔画宽度:5px;
行程:#bfc0;
宽度:100%;
高度:30px;
背景:粉红色;
}
.滚动框{
溢出y:隐藏;
指针事件:无;
背景:绿色;
高度:85vh;
.项目{
边缘底部:20px;
高度:150像素;
字体大小:36px;
背景:黄色;
}
}
&.显示滚动条.滚动框{
溢出y:滚动;
指针事件:全部;
}
}
}
Minimal示例,请输入Minimal。即使我没有检查这堵代码墙,为什么指针事件而不是简单的触摸事件呢?@kaido