嗨,亲爱的开发者们,欢迎来到本期非同不一般的技能新风向

解密:奥秘丢掉的冒泡事情

而这要从,咱们发现了一个疑似抖音小程序组件bug说起……

如图所示,这是一个交互上复刻了“抖音视频流”的小程序,用户能够像刷抖音一样,不停地往下滑动,观看新的视频。

而这个小程序,在测验的进程中,会偶现视频在滑动切换进程中卡在这条分界线的状况。

在一边排查一边探求《怎么在小程内完成类似抖音的视频翻滚播映》的进程中

却!意外层层解密了奥秘丢掉的冒泡事情

并在反复测验中,跑出完好代码得出了最佳实践事例

🔎 重要线索概览

image.png

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。

image.png image.png

经过调试能够看到,正常翻滚时绑定在 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 元素假如在接触进程中被移除,那么这个事情仍然会指向它,因此这个事情也不会冒泡到 windowdocument 目标。由此可见冒泡事情的奥秘丢掉其实也不奥秘,这便是浏览器的事情机制。

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 元素假如在接触进程中被移除,那么这个事情仍然会指向它,因此这个事情也不会冒泡到 windowdocument 目标。

—————————————————分割线—————————————————

image.png