翻车了,vue3 transition 居然有问题?

翻车了,vue3 transition 居然有问题?

1. 前言

最近遇到有用户反馈我们线上网站无法用鼠标滚轮进行滚动的 BUG,对此,我这个老码农早已经是见怪不怪,坑定是用户自己的浏览器版本太低、操作有问题、用户自己鼠标滚轮坏掉了导致的。

毕竟老夫本地测试网站好好的,怒滚几十次都完全能够流畅的滚动嘛,完全不可能是BUG呀。

于是在心里怒骂用户太菜了,鼠标滚轮坏了都不知道去修一修,居然还敢提反馈过来

2. 事情大条了

事情的转机出现在我们的测试身上,在上次用户反馈几天后的一个春日的午后,窗边洒满了秒速5厘米粉红的樱花,老夫的心情也和这樱花一样平和宁静,直到我们的测试妹子冲我怒吼 "TM的,网站滚不动了"

TM 一下就被吓醒了,心里想怎么可能,肯定是测试妹子的鼠标滚轮也 TM 坏掉了,不过好心的我还是要过去帮(接近)测试妹子看看到底啥问题

于是我慢慢踱步到测试妹子旁边,用力滚了滚鼠标滚轮,发现网站确实有滚动条,但是用鼠标滚轮进行滚动后页面居然纹丝不动?

啥情况,我又直接拖动滚动条发现并没有啥问题。

我有点慌了,切到其它网站发现鼠标滚轮居然是正常的,就我们的网站用鼠标滚轮滚动不了了

不会吧,不会吧,居然真的是我完美无瑕、举世无双的代码有问题!!!

3. 找呀找呀找问题

首先排除鼠标滚轮坏掉了,然后排除页面没有滚动条不能滚动,最后就只剩下鼠标滚轮事件被重写了。

于是我定位到滚动元素上的 事件监听表 里面,把 wheel 相关的事件全部 remove 掉之后,发现页面终于能够正常滚动了。

那我感觉问题就比较简单明确了,坑定是有人在 onMounted 中绑定了鼠标滚轮事件,但是在 onUnMounted 中没有删除鼠标滚轮事件。

待老夫查查提交记录,一定要一把揪出这个 害人精 好好批判一番,顺便看看能不能 一杯下午茶

4. 事情也许没有想象的那么简单

当然和你想的一样,事情没有想象的那么简单,毕竟如果那么简单各位看官可能就看不到这篇文章了

我仔细看了看发现事件的绑定在一个第三方库上 vue-cropper,我心想这么大的第三方库,不可能出现这么大的 BUG 吧,于是我翻看了下 vue-cropper 的源码

kotlin 复制代码
<div class="vue-cropper" ref="cropper" @mouseover="scaleImg" @mouseout="cancelScale">
</div>

// 缩放图片  
scaleImg() {  
    if (this.canScale) {  
        window.addEventListener('wheel', this.changeSize, this.passive);  
    }  
},  
// 移出框  
cancelScale() {  
    if (this.canScale) {
        // 移出鼠标滚轮事件
        window.removeEventListener('wheel', this.changeSize);  
    }  
},

unmounted() {  
    window.removeEventListener("mousemove", this.moveCrop);  
    window.removeEventListener("mouseup", this.leaveCrop);  
    window.removeEventListener("touchmove", this.moveCrop);  
    window.removeEventListener("touchend", this.leaveCrop);
    // 
    this.cancelScale()  
}

仔细阅读后,发现没有问题啊,在组件销毁时,会调用 this.cancelScale 解除 wheel 事件的监听。那就是不会出现事件泄露的问题呀,毕竟谁能在组件销毁后,再往 window 上绑定事件呢?

那问题会出在哪里呢?

我又仔细复盘了一下整个过程,发现代码其实没有任何问题,问题出现在用户的操作习惯上!!!

我们的裁切组件放在一个弹窗之中,当用户点击确认按钮后,则弹窗关闭,页面中会放置裁切后的图片。

我发现:

如果用户在对图片裁切之后,点击确认按钮之后,如果鼠标朝着裁切框方向上移动,那么页面就没有办法再用鼠标滚轮进行滚动了

如果用户在对图片裁切之后,点击确认按钮之后,如果鼠标没有朝着裁切框方向上移动,则那么页面就还是正常的。

太奇怪了,我的裁切框明明已经绑定了 @mouseover="scaleImg" @mouseout="cancelScale" 并且在 unmounted 中也进行了 cancelScale 操作,为什么还会出现这种匪夷所思的问题?

5. vue transition 导致问题

反复实验之后,发现导致这个问题的根本原因,竟然是弹窗组件里面的 transition 导致的,被 transition 包裹的组件,居然不会在 unmounted 阶段立即进行销毁,而是要等到动画结束后,才会真正的销毁 DOM

可以看到再生命周期源码 中,如果判断时 transtion 的话,不会立即销毁组件,而是会延迟等待到 transiton 走完之后再销毁组件。

虽然说这样做很正常,毕竟直接销毁后,DOM 节点没有了,怎么体现出动画效果。

但是这样的话由于弹窗在消失过程中的 @mouseover="scaleImg" mouseover事件 又会重新触发,导致 wheel 事件又被绑定上了。

这个时候 unMounted 已经被触发完了,而 @mouseout 无法被触发,因为动画执行完毕后 DOM 被销毁了。

然后页面就无法滚动了,因为绑定的 wheel 事件已经被泄露了。

具体可以看 Add inert attribute during Leave transitions 对于这个transiton导致的事件泄露的讨论

6. 解决问题

知道问题所在,解决的方式就有很多了

  1. 移出时添加遮罩层解决,这种方式主要会有多余的 DOM 节点,不介意其实问题也不大
  2. 移出时设置 inert 属性

重点是防止在组件 transition 过程中发生 点击、移入、移出 等等事件的触发即可

7. 结语

我个人感觉 vue 这块可能有设计方面的缺陷,按道理来说 unMounted 应该是能够完全处理所有组件的副作用的,而不是需要通过 hack 的方式来解决这个问题,但是可能由于水平不够,我不太完全了解 vue 这么做的理由。

然后的话就是 BUG 这东西如影随形啊,千算万算没有算到还有这种问题等着你,只是一个正常运行的弹窗还有一个正常运行的裁切组件。放到一起之后,最后导致页面都不能滚动的大 BUG

相关推荐
莹雨潇潇7 分钟前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr15 分钟前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho1 小时前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常2 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
太阳花ˉ2 小时前
html+css+js实现step进度条效果
javascript·css·html
小白学习日记3 小时前
【复习】HTML常用标签<table>
前端·html
程序员大金3 小时前
基于SpringBoot+Vue+MySQL的装修公司管理系统
vue.js·spring boot·mysql
john_hjy3 小时前
11. 异步编程
运维·服务器·javascript
风清扬_jd3 小时前
Chromium 中JavaScript Fetch API接口c++代码实现(二)
javascript·c++·chrome