如何判断一个DOM元素正在动画,一个CSS“阻塞”JS的例子
一般情况下CSS不会直接影响JS的程序逻辑,但是以CSS实现动画的话,这个便不太确定了,这个故事发生在与UED迁移全局样式的过程。
曾经我有一段实现弹出层隐藏动画的代码是这个样子的:
1 if (this.needAnimat && typeof this.animateHideAction == 'function' && this.status != 'hide') { 2 this.animateHideAction.call(this, this.$el); 3 } else 4 this.$el.hide();
在所有组件中,如果设置了animatHideAction回调的,便会执行其中的动画逻辑,针对弹出层来说:
① alert
② loading
③ toast
④ 底部弹出层
等组件中动画效果各不相同:
① 动画显示时下沉,隐藏时上浮
② 动画渐隐渐显
③ 组件底部弹出
......
针对通用的动画,一般框架会提供一段CSS类做处理,不满足的情况,各个业务团队便需要自己封装:
1 cm-fade-in, .cm-fade-out, .cm-down-in, .cm-down-out, .cm-up-in, .cm-up-out { 2 -webkit-animation-duration: 0.3s; 3 animation-duration: 0.3s; 4 -webkit-animation-fill-mode: both; 5 animation-fill-mode: both; 6 } 7 ...... 8 @keyframes fadeOut { 9 0% { 10 opacity: 1; 11 -webkit-transform: scale(1); 12 transform: scale(1); 13 } 14 100% { 15 opacity: 0; 16 -webkit-transform: scale(1.185); 17 transform: scale(1.185); 18 } 19 } 20 ......
这个时候我们要实现一个居中弹出层渐隐的效果事实上只需要这样做:
1 el.addClass('cm-fade-out'); 2 3 el.one($.fx.animationEnd, function () { 4 el.removeClass('cm-fade-out'); 5 el.hide(); 6 });
在动画结束后将对应的动画class移除,再执行真实的hide方法,隐藏dom结构。
其实,我记得是去年的时候我是这么处理这个代码的,当时被一个同事骂了不严谨,今年就使用了animationEnd接口:
1 el.addClass('cm-fade-out'); 2 3 setTimeout(function () { 4 el.removeClass('cm-fade-out'); 5 el.hide(); 6 }, 340);
这里问题来了,使用animationEnd与setTimeout去除动画class,或者执行业务真实逻辑,到底哪家强,哪个合适?
第一反应都是认为animationEnd比较合理,于是我最近遇到了一个问题:
请求一个数据,loading一直在那里转,永远不消失了!而且执行了hideLoading的操作,与数据延迟毫无关系
于是我开始愉快的定位,当时搞了一会,发现loading的动画没有执行,仔细一定位,发现css中的动画相关的css丢了,于是造成的结果是:
el.addClass('cm-fade-out');
这个代码变成了单纯的class增加,并没有执行动画,也就是,animationEnd的事件没有触发,于是没有执行hide方法,所以loading框就一直在那里转
问题定位到了,解决方案就非常简单了,将css的动画加上即可;但是也说明了,这段代码中JS代码逻辑依赖了CSS相关,从而导致了CSS阻塞JS的假象
这里如果使用setTimeout的话虽然感觉没有animationEnd严谨,但是一定会保证这逻辑代码执行,从某种程度来说,似乎更好,这里的优化代码是:
1 var isTrigger = false; 2 3 el.addClass(scope.animateOutClass); 4 5 el.one($.fx.animationEnd, function () { 6 isTrigger = true; 7 el.removeClass(scope.animateOutClass); 8 el.hide(); 9 }); 10 11 setTimeout(function () { 12 if (isTrigger) return; 13 14 el.removeClass(scope.animateOutClass); 15 el.off($.fx.animationEnd); 16 el.hide(); 17 }, 350);
如果animationEnd执行了便不理睬setTimeout,否则便走setTimeout逻辑,也不至于影响业务逻辑,但是这个似乎不是最优解决方案。
因为我没有办法,因为这里得有350ms的延迟,在不存在css动画的时候,似乎整个弹出层消失逻辑都变得2B了起来,比较好的方式是,我在执行动画前检测是否具有该css比较靠谱
所以,javascript检测CSS的某一个className是否存在,似乎变成了关键,但是就算就算能找到具有某class,这个class也未必具有动画属性,或者该属性被篡改
况且使用document.styleSheets方式去判断某个样式class是否存在,经过之前的经验,本身就是大坑,还会有跨域什么的场景,坑死人,比如这个代码:
1 function getAllSelectors() { 2 var ret = []; 3 for (var i = 0; i < document.styleSheets.length; i++) { 4 var rules = document.styleSheets[i].rules || document.styleSheets[i].cssRules; 5 for (var x in rules) { 6 if (typeof rules[x].selectorText == 'string') ret.push(rules[x].selectorText); 7 } 8 } 9 return ret; 10 } 11 12 function selectorExists(selector) { 13 var selectors = getAllSelectors(); 14 for (var i = 0; i < selectors.length; i++) { 15 if (selectors[i] == selector) return true; 16 } 17 return false; 18 } 19 20 //调用方式 21 selectorExists('.class'); 22 selectorExists('#id');
上面的代码,本身比较完善了,但是如果某一个css文件跨域的话就完蛋,所以这个方案不靠谱:
① class检测方案本身不靠谱
② 就算class靠谱,也不能保证class就具有动画相关属性,所以也不靠谱!
最终我想到的方案还是对动画属性做检测,检测点主要在动画属性的检测,比如关键属性:
① animation-name
② transition的检测
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <title></title> 5 <script id="others_zepto_10rc1" type="text/javascript" class="library" src="http://sandbox.runjs.cn/js/sandbox/other/zepto.min.js"></script> 6 <style> 7 8 .cm-fade-in { 9 -webkit-animation-name: fadeIn; 10 animation-name: fadeIn; 11 } 12 13 .cm-fade-out { 14 -webkit-animation-name: fadeOut; 15 animation-name: fadeOut; 16 } 17 18 @-webkit-keyframes fadeIn { 19 0% { 20 opacity: 0; 21 -webkit-transform: scale(0.815); 22 transform: scale(0.815); 23 } 24 100% { 25 opacity: 1; 26 -webkit-transform: scale(1); 27 transform: scale(1); 28 } 29 } 30 31 @keyframes fadeIn { 32 0% { 33 opacity: 0; 34 -webkit-transform: scale(0.815); 35 transform: scale(0.815); 36 } 37 100% { 38 opacity: 1; 39 -webkit-transform: scale(1); 40 transform: scale(1); 41 } 42 } 43 @-webkit-keyframes fadeOut { 44 0% { 45 opacity: 1; 46 -webkit-transform: scale(1); 47 transform: scale(1); 48 } 49 100% { 50 opacity: 0; 51 -webkit-transform: scale(1.185); 52 transform: scale(1.185); 53 } 54 } 55 @keyframes fadeOut { 56 0% { 57 opacity: 1; 58 -webkit-transform: scale(1); 59 transform: scale(1); 60 } 61 100% { 62 opacity: 0; 63 -webkit-transform: scale(1.185); 64 transform: scale(1.185); 65 } 66 } 67 .cm-down-in { 68 -webkit-animation-name: downIn; 69 animation-name: downIn; 70 } 71 72 .cm-down-out { 73 -webkit-animation-name: downOut; 74 animation-name: downOut; 75 } 76 77 @-webkit-keyframes downIn { 78 0% { 79 opacity: 0; 80 -webkit-transform: translate3d(0, 100%, 0); 81 transform: translate3d(0, 100%, 0); 82 } 83 100% { 84 opacity: 1; 85 -webkit-transform: translate3d(0, 0, 0); 86 transform: translate3d(0, 0, 0); 87 } 88 } 89 90 @keyframes downIn { 91 0% { 92 opacity: 0; 93 -webkit-transform: translate3d(0, 100%, 0); 94 transform: translate3d(0, 100%, 0); 95 } 96 100% { 97 opacity: 1; 98 -webkit-transform: translate3d(0, 0, 0); 99 transform: translate3d(0, 0, 0); 100 } 101 } 102 @-webkit-keyframes downOut { 103 0% { 104 opacity: 1; 105 -webkit-transform: translate3d(0, 0, 0); 106 transform: translate3d(0, 0, 0); 107 } 108 100% { 109 opacity: 0; 110 -webkit-transform: translate3d(0, 100%, 0); 111 transform: translate3d(0, 100%, 0); 112 } 113 } 114 @keyframes downOut { 115 0% { 116 opacity: 1; 117 -webkit-transform: translate3d(0, 0, 0); 118 transform: translate3d(0, 0, 0); 119 } 120 100% { 121 opacity: 0; 122 -webkit-transform: translate3d(0, 100%, 0); 123 transform: translate3d(0, 100%, 0); 124 } 125 } 126 .cm-up-in { 127 -webkit-animation-name: upIn; 128 animation-name: upIn; 129 } 130 131 .cm-up-out { 132 -webkit-animation-name: upOut; 133 animation-name: upOut; 134 } 135 </style> 136 </head> 137 <body> 138 <script type="text/javascript"> 139 var hasAnimationProperty = function (className) { 140 var animateProprtys = [ 141 //有什么判断的便新增,暂时只判断animation,不同的动画特性,判断方式不一致 142 // $.fx.cssPrefix + 'transition', 143 $.fx.cssPrefix + 'animation-name' 144 ]; 145 var el = $('<div></div>'); 146 $('body').append(el); 147 148 var i, len; 149 150 //赋予其class 151 el.attr('class', className); 152 153 for (i = 0, len = animateProprtys.length; i < len; i++) { 154 if (el.css(animateProprtys[i]) != 'none') return true; 155 } 156 s = ''; 157 return false; 158 }; 159 160 //false 161 console.log(hasAnimationProperty('test')); 162 //true 163 console.log(hasAnimationProperty('cm-up-out')); 164 //true 165 console.log(hasAnimationProperty('cm-up-in')); 166 167 </script> 168 </body> 169 </html>
核心代码:
1 var hasAnimationProperty = function (className) { 2 var animateProprtys = [ 3 //有什么判断的便新增,暂时只判断animation,不同的动画特性,判断方式不一致 4 // $.fx.cssPrefix + 'transition', 5 $.fx.cssPrefix + 'animation-name' 6 ]; 7 var el = $('<div></div>'); 8 $('body').append(el); 9 10 var i, len; 11 12 //赋予其class 13 el.attr('class', className); 14 15 for (i = 0, len = animateProprtys.length; i < len; i++) { 16 if (el.css(animateProprtys[i]) != 'none') return true; 17 } 18 s = ''; 19 return false; 20 }; 21 22 //false 23 console.log(hasAnimationProperty('test')); 24 //true 25 console.log(hasAnimationProperty('cm-up-out')); 26 //true 27 console.log(hasAnimationProperty('cm-up-in'));
如此一来,便能判断该class是否具有样式属性了,但是这个代码还需要扩展,而且这么也有性能损害,其中涉及到dom操作了,但是想想动画造成到gpu负担,好像也没什么问题