Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v-for 与 transition 同时应用时频繁更新下元素存在清理不干净的情况 #2452

Closed
ClassicOldSong opened this issue Mar 14, 2016 · 25 comments

Comments

@ClassicOldSong
Copy link

Vue.js version

1.0.18

Reproduction Link

https://jsfiddle.net/ClassicOldSong/1utjmu60/5/

操作方法:点击Start filling,然后点击Start removing,观测结果即可
remove的时间间隔比fill要小,也就是说不应该出现多余残留的元素
而且这里的remove是直接清空数组,更不应该产生残留了
然而你仍然能看到元素慢慢地变多起来

console里有输出beforeLeave和afterLeave的触发次数,可以发现afterLeave的次数小于beforeLeave

Steps to reproduce

用v-for="data in data"创建一个列表模板,设置transition属性
至少创建退出动画,进入动画无关这个问题
(为了使效果更加明显建议退出后状态不要将高度设置为0或者将透明度设置为0)
创建$vm,绑定到刚刚创建的模板

情况1

连续快速地重复向$vm.data中添加然后删除对象的动作多次

情况2

向$vm.data中添加大量对象
然后清空$vm.data
然后立即向$vm.data中添加对象
连续快速地重复以上步骤多次

正常情况下情况2的效果比情况1明显一些

What is Expected?

元素被正常地移除并不再出现在Document内

What is actually happening?

部分元素残留在页面内没有被清除且仍然能够正常地响应事件
经过测试发现afterLeave事件被触发的次数有很大的几率比beforeLeave和leave事件的触发次数小,尤其在低性能设备上比如手机会更加严重
beforeLeave事件触发次数与leave事件触发次数始终保持一致,初步判断为leave事件的回调没有被正确地执行或者在执行前就被清理掉了

请尽快修复这个BUG!!谢谢!

@taoche
Copy link

taoche commented Mar 14, 2016

我觉得 这应该不是 vue的 一个bug。

而是使用上存在一些「未知」的问题。

比如 : 你的 css leave 的动画 没有添加,导致 「退场」动画没有执行。

所以安装 vue 中的 transitionend or annimationend 没有执行, 进而导致 元素会 出现如你描述的「清理不干净」的情况。

这个问题 我在之前的开发中也遇到过,检查后确定是自己的问题。

建议你先自检一遍,如果还有这样的问题。 建议 可以在 jsfiddle.net 上使用示例代码的方式来更方便的 debug

@ClassicOldSong
Copy link
Author

实际上退场动画已经执行了,因为部分元素已经退出,还残留了一部分元素没有被清理。
没有被清理的元素的CSS退场动画也执行完毕,唯一缺少的就是将元素从document中删除
用js钩子检测发现最后一步afterLeave并没有被触发
刚刚又试了一下发现其实stagger貌似能防止一点,但具体能不能完全防止这个问题还不知道

@ClassicOldSong
Copy link
Author

stagger并没有解决问题。。。。

@yyx990803
Copy link
Member

我机器按你的两个描述都无法重现,请上代码吧。

@ClassicOldSong
Copy link
Author

@yyx990803

Reproduction Link

https://jsfiddle.net/ClassicOldSong/1utjmu60/5/

操作方法:点击Start filling,然后点击Start removing,观测结果即可
remove的时间间隔比fill要小,也就是说不应该出现多余残留的元素
而且这里的remove是直接清空数组,更不应该产生残留了
然而你仍然能看到元素慢慢地变多起来

console里有输出beforeLeave和afterLeave的触发次数,可以发现afterLeave的次数小于beforeLeave

@taoche
Copy link

taoche commented Mar 18, 2016

我认为你的demo产生的问题.... 和vue没有关系啊...

@ClassicOldSong
Copy link
Author

@taoche 但是这里没有任何其他途径操作元素啊。。。所以你觉得问题出在了哪里。。。

@YidingW
Copy link

YidingW commented Mar 20, 2016

I think the reason causing your problem is setInterval(). Since javascript is single thread, the stop setInterval() will be queued and cannot be executed until the first one finish.

@ClassicOldSong
Copy link
Author

@YidingW Actually in production environment I did not use setInterval(), all the actions were triggered by user, definitely no setInterval(). I'm working on a online chat room, where users could recall their message. But I found that when they do this too frequently there will left some elements on page. Here I just use setInterval() to simulate this action. Now I just add a timer on beforeLeave, at the time when the exiting animation should be finished if the element wasn't removed properly, jquery would remove it from document. This is just a temporary solution to this problem.

@EveLuty
Copy link

EveLuty commented Mar 22, 2016

你好,这个问题出现在于你的css中,并不是data的问题。
.trans-transition { transition: opacity .5s; opacity: 1; }
想一下,虽然你添加数据是每隔51毫秒,但是它的动画效果将会持续500毫秒,那么是不是会造成效果还没结束,数据已经被清除? 导致有些元素会继续留在doc上,但是数据上无法再去清除它。而其他效果已经结束的元素就会被很好的清除

只需要调整css效果时间,或者考虑改变下你应用的机制。我想知道了哪里出的问题,你应该很快能找到解决方案。

@ClassicOldSong
Copy link
Author

@EveLuty 实际上Vue的工作机制是这样的:当发现你的数据变动以后才会去进行元素操作比如添加或者移除。而我遇到的问题是Vue已经发现了数据变化,而且知道这个元素需要被清除,已经执行了退场动画,却在最后一步的处理上出了问题。所以我并不是十分认同你的看法。

@EveLuty
Copy link

EveLuty commented Mar 22, 2016

你所谓的最后一步的处理出了问题,并不然,持续产生阴影的原因是你在dome中永不停歇的push。所以for一直在执行。即使你使得data=[], 那么数据也会在你设定好的动画中渐进消失,在动画结束之前并不会立刻清空,而你又持续push,所以导致这样。
你可以打开控制台看下,可以观察到只有当过场动画的class依次都变完之后,该元素才会消失,数据也才会消失。

而阴影滞留呢,无一例外都是data设为空集合之后新添加数据造成的。是因为是新push的数据还未完全走完过渡效果,就原数据被清空造成的。导致在离开效果进行时,已经丢失了跟原数据的绑定。但是这种应用案例应该并不多吧。

由css过渡效果时间引起的问题。我不确定这只发生于vuejs,或者是针对所有类似情况都会发生。

最后,有两个方法可以解决这个问题,
一是取消效果的延迟效果(从此也看出了问题出在transition的时间上)
trans-transition { transition: opacity 0s; opacity: 1; }
但是你可能想要保留渐进效果。
那么可以尝试用第二种方法,来避免因为数据的删除,而导致动画有可能会终止于leave的状态(即opacity:0.1)

.trans-transition {
    transition: opacity .5s;
    opacity: 1;
}
.trans-enter{
    opacity: 0.1;
}

.trans-leave {
    opacity: 0;
}

这种方案在使用后,依然能看到有三到四行残留,是因为你在不停push,正常现象。

就这样了。如果还有问题,就等尤大大回复吧~~

@ClassicOldSong
Copy link
Author

@EveLuty 我设置为opacity: 0.1只是为了更直观地展现这个问题。。因为在实际应用中我的处理方式就是opacity: 0。具体应用场景我在回复YidingW的时候已经说明,我无法阻止用户在动画结束之前向里面推送新的消息。而且你可以尝试一下将 data=[] 换成 data.splice(data.length - 1, 1) 之类的操作方式,一样会出现问题(比如https://jsfiddle.net/ClassicOldSong/1utjmu60/7/ ,stop后依旧会有残留,而且让我很纳闷的是,数据绑定如果已经丢失的话为何还能继续响应click事件)。

所以估计只能等待尤大大回复了。。。

@EveLuty
Copy link

EveLuty commented Mar 22, 2016

恩,按opacity:0的方式就都没有问题了。

问题还是在于动画时间导致数据删除并不及时,我又测试了下,如果你在start的里面加入简单的console查看data的长度,会有概率出现等于2的时候,就是说在push之前,数据中并不为空,也验证了我说的,清空在更短周期内执行,并不代表会比push之前完成清空数组的任务,导致出现这种问题。

(另外,如果这两个方法的时间周期如果一致,也不会出现问题,并且动画数量也一致)

至于最后的疑问,数据绑定丢失,但是你的html中该元素的click事件还是指向vue里的方法呀 :) 你可以看看。

@ClassicOldSong
Copy link
Author

@EveLuty 我的意思是。。。我在opacity: 0的时候发现了这个问题。。。。我纳闷的是传入参数而不是click事件本身,也就是说Vue内部的引用对象其实也没有被清理干净
有空仔细扒一下Vue源码好了。。
不过确实像我这样奇葩的应用场景的确很罕见,然而确实有问题
顺便实际场景下用户的操作会比这里的模拟要随机的多
另外现在我对于残留元素的处理方式是用jQuery删掉((

补充:刚刚尝试了一下在fill的循环里检测datas长度,无论是直接vm.datas还是vm.$data.datas的长度都没有发现不为0的情况,在我看来以及从数学的角度上来分析是fill之前已经清空了,麻烦指点一下如何正确地追踪。。。

@EveLuty
Copy link

EveLuty commented Mar 22, 2016

首先,你真的按我的css去试过嘛? 你去试试,一旦点击stop后不会有一点残留。难道这不是你要的结果么
在点击stop之前依然能看到有三到四行残留刷新着,是因为你在不停push,正常现象。stop后不会有无法remove的’阴影‘

*刚测试了下,不用非得opacity等于0,只要entry和leave不同即可(当然leave的opacity不能为1)。leave的opacity越小,表现越好。但是stop之后效果都能达到,无不可移除的阴影元素

@EveLuty
Copy link

EveLuty commented Mar 22, 2016

追踪么,不要加太多语句;就这样,然后在稍微低一点的性能下测试就会很明显的发现问题。就像你说的手机性能会更严重一样(我在笔记本的节能模式下测试过,会很明显)

start: function() {
        var _this = this;
        fill_ = setInterval(function() {
        _this.datas.push("Test String " + _this.datas.length);
        console.log(_this.datas.length);
      }, 51);
    },

@ClassicOldSong
Copy link
Author

@EveLuty 十分神奇,进入和退出不同居然能解决我遇到的问题hhh
不过这仍然是一个问题啊,为何相同就会导致问题,哪怕进入和退出的时候都是0
所以这个issue还是暂时先继续开着好了

@yyx990803
Copy link
Member

别着急我这两天就看...

@yyx990803
Copy link
Member

好吧,这个问题其实我一直都知道,原因其实比较坑爹,和浏览器的 timer / 渲染机制有关系。举例来说:

  1. 元素被插入 DOM,此时 opacity 为 0,然后下一帧 enter class 被移除,触发 CSS transition; 但是这个 transition 要等到下一个浏览器的渲染 tick(也就是 requestAnimationFrame 的时候)才启动,在那之前,元素的 opacity 依然为 0
  2. 假如在 enter class 被移除后,CSS transition 启动之前的极小间隔触发数组更新,则浏览器会判断 opacity 的当前值和目标值相等,于是直接跳过了 transition,不触发 transitionend 事件... 没有这个事件,Vue 就不知道何时调用 afterLeave 了。

这就是为什么 enter 和 leave 用不同的值可以解决这个问题。

至于能不能在 Vue 的机制里解决这个问题,还要再看看。

@ClassicOldSong
Copy link
Author

好吧hhh我仔细考虑过后觉得也是这个问题,还是谢谢各位帮忙了~

Evan You notifications@github.com于2016年3月25日周五 上午10:54写道:

好吧,这个问题其实我一直都知道,原因其实比较坑爹,和浏览器的 timer / 渲染机制有关系。举例来说:

元素被插入 DOM,此时 opacity 为 0,然后下一帧 enter class 被移除,触发 CSS transition; 但是这个
transition 要等到下一个浏览器的渲染 tick(也就是 requestAnimationFrame
的时候)才启动,在那之前,元素的 opacity 依然为 0
2.

假如在 enter class 被移除后,CSS transition 启动之前的极小间隔触发数组更新,则浏览器会判断 opacity
的当前值和目标值相等,于是直接跳过了 transition,不触发 transitionend 事件... 没有这个事件,Vue 就不知道何时调用
afterLeave 了。

这就是为什么 enter 和 leave 用不同的值可以解决这个问题。

至于能不能在 Vue 的机制里解决这个问题,还要再看看。


You are receiving this because you authored the thread.
Reply to this email directly or view it on GitHub
#2452 (comment)

@ClassicOldSong
Copy link
Author

抱歉再次开启此issue。。
这个问题在很大程度上已经解决了,但是实际应用中还是会不定期出现残留,想要reproduction的话恐怕比较困难了。。。
希望能够在文档里引用一下 @EveLuty 的解决方案,谢谢

@yyx990803
Copy link
Member

@ClassicOldSong 应该是在不支持 rAF 的旧浏览器吧... 这个真没办法

@ClassicOldSong
Copy link
Author

@yyx990803 并不是。。。在最新的Chrome下也出现了

@yyx990803
Copy link
Member

@ClassicOldSong 这... 那保险起见只能用 0.00001 大法了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants