嗨,亲爱的开发者们,欢迎来到本期非同不一般的技能新风向
解密:奥秘丢掉的冒泡事情
而这要从,咱们发现了一个疑似抖音小程序组件bug说起……
如图所示,这是一个交互上复刻了“抖音视频流”的小程序,用户能够像刷抖音一样,不停地往下滑动,观看新的视频。
而这个小程序,在测验的进程中,会偶现视频在滑动切换进程中卡在这条分界线的状况。
在一边排查一边探求《怎么在小程内完成类似抖音的视频翻滚播映》的进程中
却!意外层层解密了奥秘丢掉的冒泡事情
并在反复测验中,跑出完好代码得出了最佳实践事例
🔎 重要线索概览
v1. 排查|swiper + video 组件简略完成视频翻滚播映
小程序内怎么完成类似于抖音的翻滚视频播映呢?了解小程序的同学天然会联想到使用 swiper
+ video
组件的完成途径,这的确是完成翻滚视频播映的有效途径。但考虑到每个视频都需发起网络请求,假如在 swiper
组件中直接烘托出一切的 video
组件,必然会引起功能问题。
因此咱们在每个 swiper-item
默认烘托视频的封面图片,当滑动到对应视频封面后才开端加载相关视频,滑动脱离后将视频毁掉。让咱们简略完成一个 demo 看下播映作用。
// ttml 代码示例
<swiper
vertical="{{true}}"
circular="{{true}}"
current="{{currSwiperIdx}}"
style="height:{{windowHeight}}px"
bindchange="swiperChange">
<block tt:for="{{imgArr}}">
<swiper-item bindtouchmove="move" bindtouchstart="start" bindtouchend="end">
<view class="main" style="height:{{windowHeight}}px;">
<block tt:if="{{showMediaPosterBg || index !== currIdx}}">
<image style="width:100%; height:100%;" src="{{item}}" mode="" />
</block>
<block tt:elif="{{isLoadFinish}}">
<video
class="video"
style="height:{{windowHeight}}px;"
src="{{urlArray[index]}}"
object-fit="cover"
show-fullscreen-btn="{{false}}"
show-play-btn="{{false}}"
controls="{{false}}"
autoplay="{{true}}"
bindplay="playerPlay">
<image tt:if="{{showVideoImgBg}}" class="absolute_fix" src="{{item}}" mode="" />
</video>
</block>
</view>
</swiper-item>
</block>
</swiper>
完好代码链接:microapp.bytedance.com/ide/minicod…
聪明的开发者能够发现 swiper
+ video
组件的方法确实能够完成视频的翻滚播映,但是在翻滚进程中 swiper
组件会呈现滑动动画中止。
v2.探索|swiper 组件滑动动画中止问题
使用过 swiper
组件的同学应该知道,swiper
组件具有自己的动画作用,在滑动进程中,swiper
组件会根据你最后滑动逗留的位置确认是否翻滚到下一个 swiper-item
。从目前的体现来看,swiper
组件没有触发后续的翻翻滚画,所以咱们猜测是否是因为没有识别到后续的翻滚手势造成的卡顿呢?
为了验证这个猜测,咱们在 swiper-item
上绑定了三个事情:bindtouchmove="move"
,bindtouchstart="start"
,bindtouchend="end"
。在每个事情触发时,会在 vConsole 输出相应的 log。
经过调试能够看到,正常翻滚时绑定在 swiper-item
上的事情都被正常触发,但是在 swiper
组件的滑动动画中止时 bindtouchend="end"
事情并没有被触发。
这和咱们之前的猜测一致,阐明 swiper
组件的卡顿和没有识别到的手势事情相关,那么又是什么原因造成了 touchend
相关的手势的丢掉呢?
v3.解密|奥秘丢掉的冒泡事情
- 咱们知道事情冒泡指的是,事情会从最内层的元素开端产生,一向向上传播,直到最外层祖先
<html>
。其原理如图所示:
从之前的调试咱们看到 swiper-item
上绑定的 bindtouchmove="move"
,bindtouchstart="start"
事情都正常触发,这阐明事情是能够正常冒泡的,其中的怪异之处在于 最后的 bindtouchend="end"
相关的冒泡事情丢掉了。
仔细检查代码逻辑能够发现,咱们在 video
中使用了一个 image
组件作为视频封面,用于防止视频加载完成后的黑屏闪烁。
在视频加载完成后,咱们会主动毁掉该 image
组件。而咱们最开端的接触手势都是产生在这个 image
上的,所以是否是因为 image
组件的毁掉造成后续冒泡事情的丢掉呢?
v4.测验|是否由 image 组件毁掉导致
为了验证猜测,咱们复现一个最小 demo,经过 setTimeout
模拟视频加载,setTimeout
中的事情履行时会毁掉产生接触手势的 dom 元素。
// demo 的 ttml
<swiper vertical="{{true}}" style="height:100vh" bindchange="swiperChange" bindanimationfinish="swiperAniFinish" bindtransition="swiperTranstion">
<swiper-item>
<view class="item" bindtouchmove="moveOne" bindtouchstart="startOne" bindtouchend="endOne">
<view tt:if="{{show}}" class="item-one"> page one</view>
</view>
</swiper-item>
<swiper-item>
<view class="item" bindtouchmove="moveTwo" bindtouchstart="startTwo" bindtouchend="endTwo">
<view class="item-two"> page two </view>
</view>
</swiper-item>
</swiper>
// demo 的页面 js
Page({
data: {
show:true,
},
onLoad: function (options) {
},
startOne(){
console.log('---->>>>>startOne');
setTimeout(()=> {
this.setData({
show:false,
})
},1000)
},
endOne(){
console.log('---->>>>>endOne')
},
moveOne() {
console.log('---->>>>>moveOne')
},
})
完好示例代码:microapp.bytedance.com/ide/minicod…
从示例中能够看到,当滑动 swiper
组件的进程中,假如开端产生的手势的 dom 被毁掉,那么后续的手势事情就不会再触发,一起 swiper
组件呈现滑动动画中止。
v5.延展|这是抖音小程序特有的状况嘛?
进而产生了疑问,这是抖音小程序特有的状况仍是浏览器事情机制本是如此设计呢?话不多说,咱们直接上浏览器上写个最小 demo 看看具体状况。
\
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>test</title>
</head>
<body>
<div id="target">
<div id="inner" style="background: red; height:400px" >
1221212122121
</div>
</div>
</body>
</body>
<script>
const $target = document.querySelector('#target');
const $inner = document.querySelector('#inner');
$target.addEventListener('touchstart', function() {
console.log('touchstart');
setTimeout(()=>{
$inner.remove()
// $inner.setAttribute('style','display: none')
// $inner.setAttribute('style','visibility: hidden')
}, 1000);
});
$target.addEventListener('touchmove', function() {
console.log('touchmove');
});
$target.addEventListener('touchend', function() {
console.log('touchend');
});
</script>
</html>
从 chrome 上的示例能够看出,当触发 touchu 事情时,touchustart 相关的事情立刻被触发,假如在 dom 事情毁掉前完毕 touch 事情,touchend 相关事情可正常履行,但假如在 dom 毁掉后再触发 touchend 事情,相关事情则都不会被履行。
由此咱们能够看出,当前 dom 毁掉会导致产生在 dom 上的后续冒泡事情的同时毁掉。后续查阅 mdn 网站上关于 touch 事 件相关的文档,也证明了咱们的猜测:dom 元素假如在接触进程中被移除,那么这个事情仍然会指向它,因此这个事情也不会冒泡到 window
或 document
目标。由此可见冒泡事情的奥秘丢掉其实也不奥秘,这便是浏览器的事情机制。
v6.实践|怎么完成在小程内进行视频翻滚播映
更进一步,我测验假如直接使用 dom 的特点将 dom 躲藏,即使其 display 特点的值为 none,或许 visibility 特点的值为 hidden,成果又是怎样呢?
明显假如仅仅躲藏 dom 元素,那么不会影响后续 touch 事情的冒泡,因此一切事情都会正常履行。
\
v7.共享|最佳实践事例 & tips
最佳实践事例
根据上面的探索,咱们了解到了后续冒泡事情消失的原因,天然要解决 swiper 组件滑动动画中止的方法也变得明晰了,只需要让产生 touch 事情的 dom 元素不被毁掉,让后续冒泡事情顺利完成,问题便可方便的解决。
在咱们的事例中,咱们在 video
组件中,使用了 image
组件作为视频的封面,在视频加载成功后会对 image
组件进行毁掉,并且为了视频的正常展现,毁掉或躲藏 image
组件也是必不可少的。综合以上状况,咱们考虑在视频加载成功后不毁掉 image
组件,改为躲藏该组件,这样便能够让冒泡事情顺利完成。此外咱们知道,小程序供给一个 hidden 属于专门用于躲藏小程序的组件,使用 hidden 特点便能够很好的解决咱们的问题。
完好示例代码:microapp.bytedance.com/ide/minicod…
Tips ~
不要随意毁掉产生 touch 事情的内层 dom 元素,dom 元素假如在接触进程中被移除,那么这个事情仍然会指向它,因此这个事情也不会冒泡到 window
或 document
目标。
—————————————————分割线—————————————————