以编程方式使用来自 JS 的 CSS 转换的干净方式?

Posted

技术标签:

【中文标题】以编程方式使用来自 JS 的 CSS 转换的干净方式?【英文标题】:Clean way to programmatically use CSS transitions from JS? 【发布时间】:2013-09-05 01:46:52 【问题描述】:

正如标题所暗示的,有没有一种合适的方法来设置一些初始的 CSS 属性(或类)并告诉浏览器将它们转换为另一个值?

例如(fiddle):

var el = document.querySelector('div'),
    st = el.style;
st.opacity = 0;
st.transition = 'opacity 2s';
st.opacity = 1;

这不会为 Chrome 29/Firefox 23 中元素的不透明度设置动画。这是因为 (source):

[...] 你会发现,如果你同时应用两组属性,一个立即 之后,浏览器会尝试优化属性 更改,忽略您的初始属性并阻止转换。 在幕后,浏览器在绘制之前批量修改属性 虽然通常会加快渲染速度,但有时会产生不利影响 影响。

解决方案是在应用两组 特性。一个简单的方法就是访问 DOM 元素的 offsetHeight 属性 [...]

事实上,该 hack 确实适用于当前的 Chrome/Firefox 版本。更新代码(fiddle - 打开小提琴后点击Run 再次运行动画):

var el = document.querySelector('div'),
    st = el.style;
st.opacity = 0;
el.offsetHeight; //force a redraw
st.transition = 'opacity 2s';
st.opacity = 1;

然而,这是相当骇人听闻的,据报道在某些 android 设备上不起作用。

另一个answer 建议使用setTimeout 以便浏览器有时间执行重绘,但它也失败了,因为我们不知道重绘需要多长时间。猜测相当多的毫秒数(30-100?)以确保发生重绘意味着牺牲性能,不必要地闲置,希望浏览器在那段时间执行一些魔法。

通过测试,我发现另一个解决方案在最新的 Chrome 上运行良好,使用 requestAnimationFrame (fiddle):

var el = document.querySelector('div'),
    st = el.style;
st.opacity = 0;
requestAnimationFrame(function() 
    st.transition = 'opacity 2s';
    st.opacity = 1;
);

我假设requestAnimationFrame 在执行回调之前一直等到下一次重绘开始之前,因此浏览器不会批量处理属性更改。此处不完全确定,但在 Chrome 29 上运行良好。

更新:经过进一步测试,requestAnimationFrame 方法在 Firefox 23 上运行不佳 - 大多数时间似乎都失败了。 (fiddle)

是否有适当的或推荐的(跨浏览器)方法来实现这一点?

【问题讨论】:

我相信最简洁的方法是添加和删除类,而不是直接处理属性。但这不是纯 js,因为要更改的属性和值都在 CSS 中(您可以使用 js 操作样式表,但它也有点丑)。 @bfavaretto 好吧,当我从元素中添加/删除类时,也会发生同样的行为。将opacity:0opacity:1 移动到CSS 规则,然后添加给定的类会在我的测试中产生相同的结果。这是fiddle 来证明这一点。我实际上在我的实际用例中使用了类,但到目前为止,无论处理属性或类,结果都是相同的。 嗯,setTimeout 为我工作。根据MDN,requestAnimationFrame 可能更适合您的要求,因为它保证在下一次重绘时运行您的代码。 @Passerby 是的,我倾向于requestAnimationFrame,尽管来自 MDN 的措辞“在下一次重绘之前调用指定的函数来更新动画”听起来像属性更改之前可以批量处理随后进行了下一次重绘,尽管它确实有效。我可能误解了那句话,必须检查规格。 @FabrícioMatté 我认为措辞只是意味着回调将在下一次重绘时执行。只要它与“当前”队列分开,它们就不会被分批(这就是为什么我相信setTimeout 也应该工作)。在你的小提琴情况下,在requestAnimationFrame/setTimeout 内部和外部设置转换确实会有所不同:默认情况下元素有opacity:1,所以在外部设置opacity:0 transition 就可以了立即开始衰减,然后在延迟回调中,转换回 1。因此,将 transition 放入回调中对于您的小提琴至关重要。 【参考方案1】:

这是一个工作版本。自己看吧。

在 Chrome、Firefox、Opera 上测试。

在我的 firefox 版本中,它不支持 style.transition,因此如果标准名称不可用,我将其回退到供应商特定名称。

http://jsfiddle.net/eNCBz/5/

var el = document.querySelector('div');

var VENDORS = ['Moz', 'Webkit', 'Ms', 'O'];

function getVendorSpecificName(el, prop) 
    var style = el.style;
    if (prop in style) 
        return prop;
    
    prop = ucfirst(prop);
    for (var i = 0, l = VENDORS.length, name; i < l; i++) 
        name = VENDORS[i] + prop;
        if (name in style) 
            return name;
        
    
    return null;


function ucfirst(str) 
    return str && str.charAt(0).toUpperCase() + str.substring(1);


function toCamelCase(str) 
    return str.split('-').map(function (str, i) 
        return i > 0 ? ucfirst(str) : str;
    ).join('');


function animateCss(el, prop, from, to, duration) 
    var style = el.style,
        camel = toCamelCase(prop),
        vendorSpecific = getVendorSpecificName(el, camel);
    if (!vendorSpecific) 
        console.log(prop + ' is not supported by this browser');
        return false;
    

    var transitionPropName = getVendorSpecificName(el, 'transition');
    if (!(transitionPropName in style)) 
        console.log('transition is not supported by this browser');
        return false;
    

    style[vendorSpecific] = from;

    setTimeout(function () 
        style[transitionPropName] = prop + ' ' + duration + 's ease';
        setTimeout(function () 
            style[vendorSpecific] = to;
        , 1);
    , 1);
    return true;


animateCss(el, 'opacity', 0, 1, 2);

让我解释一下发生了什么:

制作了一些辅助函数,例如 ucfirst、toCamelCase

style 属性使用驼峰式命名

如果标准名称不可用,请尝试查找供应商特定的样式属性名称

利用 setTimeout 函数确保浏览器重绘

据我所知,所有浏览器总是在超时时重绘

我试图让它更通用的功能,以便其他属性也可以应用,例如颜色或背景。

希望这会有所帮助!

【讨论】:

感谢您的意见。我做了一些修改,以便它可以多次为同一个元素设置动画:jsfiddle.net/eNCBz/7 虽然似乎存在非常小的超时问题,但 Firefox 经常无法重绘(并且 Chrome 有时也会在页面/元素出现时失败过渡更复杂)。我认为 100 毫秒就足够了,但即便如此也不是一个非常干净的解决方案(异步希望浏览器魔法)。【参考方案2】:

您不需要太多的 javascript 来实现您想要的效果,只需使用 CSS 关键帧和动画即可获得相同的效果。

div 
    opacity: 0;


div.fadeIn 
    -webkit-animation: fadeIn 2s forwards;
    animation: fadeIn 2s forwards;


@keyframes fadeIn 
    0% opacity: 0;
    100% opacity: 1;


@-webkit-keyframes fadeIn 
    0% opacity: 0;
    100% opacity: 1;

如this JsFiddle 所示,它可以在页面加载(已添加类)或动态添加类以触发动画时工作。

【讨论】:

【参考方案3】:

      <script  src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
       <script>
    
         $(document).ready(function()
    $('a.hiw*').click(function()	
    	
    	id = this.id;
    	dval = $('#'+id).attr('data-value');
    	if (dval == 0) 
    		$('a.hiw*').attr('data-value','0');
    		$( ".hiw-popup" ).remove();
    		$('#'+id).attr('data-value','1');
    		$('<div class="hiw-popup white-well run-animation hidden-xs"><div class="row text-center"><div class="col-sm-2 animation-hiw col-xs-2"><span class="glyphicon glyphicon-pencil hiw-icon1" style="background:#ffffff;">1</span><br/>block1</div><div class="col-sm-2 animation-hiw col-xs-2"><span class="glyphicon glyphicon-shopping-cart hiw-icon2">2</span><br/>BLOCK3</div><div class="col-sm-3 animation-hiw col-xs-3"><span class="glyphicon glyphicon-folder-open hiw-icon3">3</span><br/>BLOCK2</div><div class="col-sm-3 animation-hiw col-xs-3"><span class="glyphicon glyphicon-ok hiw-icon4">4</span><br/>BLOCK</div><div class="col-sm-2 animation-hiw col-xs-2"><span class="glyphicon glyphicon-arrow-down hiw-icon5">5</span><br/>BLOCK</div></div></div>').insertAfter('#'+id);
    	else
    		$('#'+id).attr('data-value','0');
    		$( ".hiw-popup" ).remove();
    		
    );
    );
    var ahiw = function(id)	
    	dval = $('#'+id).attr('data-value');
    	if (dval == 0) 
    		$('a.hiw*').attr('data-value','0');
    		$( ".hiw-popup" ).remove();
    		$('#'+id).attr('data-value','1');
    		$('<div class="hiw-popup white-well run-animation hidden-xs"><div class="row text-center"><div class="col-sm-2 animation-hiw col-xs-2"><span class="glyphicon glyphicon-pencil hiw-icon1" style="background:#ffffff;">1</span><br/>block1</div><div class="col-sm-2 animation-hiw col-xs-2"><span class="glyphicon glyphicon-shopping-cart hiw-icon2">2</span><br/>BLOCK3</div><div class="col-sm-3 animation-hiw col-xs-3"><span class="glyphicon glyphicon-folder-open hiw-icon3">3</span><br/>BLOCK2</div><div class="col-sm-3 animation-hiw col-xs-3"><span class="glyphicon glyphicon-ok hiw-icon4">4</span><br/>BLOCK</div><div class="col-sm-2 animation-hiw col-xs-2"><span class="glyphicon glyphicon-arrow-down hiw-icon5">5</span><br/>BLOCK</div></div></div>').insertAfter('#'+id);
    	else
    		$('#'+id).attr('data-value','0');
    		$( ".hiw-popup" ).remove();
    	
    
       
    </script>
    /* Chrome, Safari, Opera */
    @-webkit-keyframes animation-hiw-icon 
      from 
        background-color: #d9d9d9;
      
      to 
        background-color: #4ad18f;
      
    
    /* Standard syntax */
    @keyframes animation-hiw-icon 
      from 
        background-color: #d9d9d9;
      
      to 
        background-color: #4ad18f;
      
    
    
    
    /* Chrome, Safari, Opera */
    @-webkit-keyframes animation-hiw-prog 
      from 
       background-color: #d9d9d9;
        width: 0%
      
      to 
        width: 100%;
    	 background-color: #4ad18f;
      
    
    /* Standard syntax */
    @keyframes animation-hiw-prog 
      from 
        width: 0%
      
      to 
        width: 100%;
      
    
    /* Chrome, Safari, Opera */
    @-webkit-keyframes animation-hiw-pop 
      from 
        opacity: 0.5;
    	background-color: #d9d9d9;
        -ms-transform: scale(0.8); /* IE 9 */
        -webkit-transform: scale(0.8); /* Chrome, Safari, Opera */
        transform: scale(0.8);
      
      to 
       background-color: #4ad18f;
        opacity: 1;
        font-weight: normal;
        -ms-transform: scale(.8); /* IE 9 */
        -webkit-transform: scale(.8); /* Chrome, Safari, Opera */
        transform: scale(.8);
      
    
    /* Standard syntax */
    @keyframes animation-hiw-pop 
      from 
      background-color: #d9d9d9;
        opacity: 0.5;
        -ms-transform: scale(0.8); /* IE 9 */
        -webkit-transform: scale(0.8); /* Chrome, Safari, Opera */
        transform: scale(0.8);
      
      to 
      background-color: #4ad18f;
        opacity: 1;
        font-weight: normal;
        -ms-transform: scale(.8); /* IE 9 */
        -webkit-transform: scale(.8); /* Chrome, Safari, Opera */
        transform: scale(.8);
      
    
    /*Animation Trigger*/
    .run-animation .hiw-progress:after, .run-animation .animation-hiw, .run-animation .hiw-icon1, .run-animation .hiw-icon2, .run-animation .hiw-icon3, .run-animation .hiw-icon4, .run-animation .hiw-icon5 
      -webkit-animation-play-state: running; /* Safari and Chrome */ 
      animation-play-state: running;
    
    
    
    .run-animation .hiw-progress:after, .run-animation .animation-hiw, .run-animation .hiw-icon1, .run-animation .hiw-icon2, .run-animation .hiw-icon3, .run-animation .hiw-icon4, .run-animation .hiw-icon5 
      -webkit-animation-play-state: running;
      animation-play-state: running;
    
    .hiw-progress:after 
      content: "";
      width: 0%;
      height: 5px;
      background: #4ad18f;
      display: inline-block;
      position: absolute;
      top: 0;
      left: 0;
      -webkit-animation: animation-hiw-prog 5s linear forwards;
      animation: animation-hiw-prog 5s linear forwards;
      -webkit-animation-play-state: paused;
      animation-play-state: paused;
    
    .white-well 
      background-color: #fff;
      padding: 10px 15px;  border-radius: 5px;
      border: 1px solid #f1f1f1;
    
    .hiw-popup 
      position: absolute;
       width: 100%;
      z-index: 9;
      margin: 30px 0 0 -15px;
      padding: 0px 15px !important;
      border-color: rgba(0, 0, 0, 0.25) !important;
      box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.60);
      -webkit-box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.60);
      -mz-box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.60);
    
    .hiw-popup .arrow 
      position: absolute;
      display: block;
      width: 0;
      height: 0;  border-color: transparent;
      border-style: solid;
      border-width: 11px;
      left:90%;
      margin-left: -11px;
      border-top-width: 0;
      border-bottom-color: rgba(0, 0, 0, 0.25);
      top: -11px;
    
    .hiw-popup .glyphicon 
      margin-bottom: 10px;
      margin-right: 0px !important;font-weight:bold;
      background-color: #ffffff;color:#222222 !important;
    
    .white-well .glyphicon 
       background-color: #ffffff!important;
      border-radius: 76px;margin-top: -3px;color:#d9d9d9 !important;
      padding: 5px 9px 8px;
      color: #fff;
      box-shadow: 0px 0px 3px #222222;
      border: 3px solid #ffffff;
    
    .glyphicon 
      position: relative;
      top: 1px;
      display: inline-block;
      font-family: 'Glyphicons Halflings';
      font-style: normal;
      font-weight: normal;
      line-height: 1;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
    
    .clearfix:before, .clearfix:after, .container:before, .container:after, .container-fluid:before, .container-fluid:after, .row:before, .row:after, .form-horizontal .form-group:before, .form-horizontal .form-group:after, .btn-toolbar:before, .btn-toolbar:after, .btn-group-vertical > .btn-group:before, .btn-group-vertical > .btn-group:after, .nav:before, .nav:after, .navbar:before, .navbar:after, .navbar-header:before, .navbar-header:after, .navbar-collapse:before, .navbar-collapse:after, .modal-footer:before, .modal-footer:after, .review:before, .review:after, .panel-body:before, .panel-body:after 
      content: " ";
      display: table;
    
    .animation-hiw:nth-child(1) 
      -webkit-animation-delay: .2s;
      animation-delay: .2s;
    
    
    .hiw-icon5 
      -webkit-animation: animation-hiw-icon 0.2s forwards;
      animation: animation-hiw-icon 0.2s forwards;
      -webkit-animation-delay: 5s;
      animation-delay: 5s;
      -webkit-animation-play-state: paused;
      animation-play-state: paused;
    
    .hiw-icon4 
      -webkit-animation: animation-hiw-icon 0.2s forwards;
      animation: animation-hiw-icon 0.2s forwards;
      -webkit-animation-delay: 3.75s;
      animation-delay: 3.75s;
      -webkit-animation-play-state: paused;
      animation-play-state: paused;
    
    .hiw-icon3 
      -webkit-animation: animation-hiw-icon 0.2s forwards;
      animation: animation-hiw-icon 0.2s forwards;
      -webkit-animation-delay: 2.25s;
      animation-delay: 2.25s;
      -webkit-animation-play-state: paused;
      animation-play-state: paused;
    
    .hiw-icon2 
      -webkit-animation: animation-hiw-icon 0.2s forwards;
      animation: animation-hiw-icon 0.2s forwards;
      -webkit-animation-delay: 1s;
      animation-delay: 1s;
      -webkit-animation-play-state: paused;
      animation-play-state: paused;
    
    .hiw-icon1 
      -webkit-animation: animation-hiw-icon 0.2s forwards;
      animation: animation-hiw-icon 0.2s forwards;
      -webkit-animation-delay: .2s;
      animation-delay: .2s;
      -webkit-animation-play-state: paused;
      animation-play-state: paused;
    
    
    .animation-hiw 
      -webkit-animation: animation-hiw-pop 0.2s forwards; /* Chrome, Safari, Opera */
      animation: animation-hiw-pop 0.2s forwards;
      -webkit-animation-play-state: paused; /* Safari and Chrome */
      animation-play-state: paused;
      opacity: 0.5;
      -ms-transform: scale(0.8); /* IE 9 */
      -webkit-transform: scale(0.8); /* Chrome, Safari, Opera */
      transform: scale(0.8);
        background: #d9d9d9;
       width: 15%;
      padding: 2% 1%;
      height: 140px;
      color: #ffffff;  float: left;
    
    .animation-hiw:nth-child(1) -webkit-animation-delay: .2s; animation-delay: .2s; 
    .animation-hiw:nth-child(2) -webkit-animation-delay: 1s; animation-delay: 1s; 
    .animation-hiw:nth-child(3) -webkit-animation-delay: 2.25s; animation-delay: 2.25s; 
    .animation-hiw:nth-child(4) -webkit-animation-delay: 3.75s; animation-delay: 3.75s; 
    .animation-hiw:nth-child(5) -webkit-animation-delay: 5s; animation-delay: 5s; 
    
    hiw 
      visibility: hidden;
      font-size: 12px;
      font-style: italic;
      text-align: right;
      float: right;
    
    <body>
    <a href="javascript:void(0);" class="hiw hidden-xs" id="hiw_1" data-value="1" style="float:LEFT;margin-right:10px;color: #4ad18f;font-size: 12px;padding:0px 0px 5px 0px;">How it works</a>
    </body>

    #

【讨论】:

【参考方案4】:

目前还没有一个干净的方法(不使用 CSS 动画 - 请参阅 the nearby answer by James Dinsdale 以获取使用 CSS 动画的示例)。有一个 spec bug 14617,很遗憾,自 2011 年提交以来一直没有采取行动。

setTimeout 在 Firefox (this is by design) 中无法可靠运行。

我不确定requestAnimationFrame - 对原始问题的编辑说它也不能可靠地工作,但我没有调查。 (更新:它看起来像 requestAnimationFrame is considered at least by one Firefox core developer to be the place where you can make more changes, not necessarily see the effect of the previous changes。)

强制重排(例如通过访问offsetHeight)是一种可能的解决方案,但要使转换工作,它应该足以强制重新设置样式(即getComputedStyle:https://timtaubert.de/blog/2012/09/css-transitions-for-dynamically-created-dom-elements/

window.getComputedStyle(elem).opacity;

请注意,仅运行 getComputedStyle(elem) 是不够的,因为它是惰性计算的。我相信无论您从 getComputedStyle 询问哪个属性,重新样式仍然会发生。请注意,要求几何相关的属性可能会导致更昂贵的回流。

更多关于回流/重新设计/重绘的信息:http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/

【讨论】:

物业询问是否重要。它必须是用于 CSS 过渡的那个。否则,此技巧将无法在 Chrome 或 Edge 上运行。但是,无论访问的属性如何,它都可以在 Firefox 上正常运行。【参考方案5】:

自 2013 年以来情况发生了变化,所以这里有一个新的答案:

您可以使用Web Animations。它们在 Chrome 36 和 Firefox 40 中原生实现,有 a polyfill for all the other browsers。

示例代码:

var player = snowFlake.animate([
  transform: 'translate(' + snowLeft + 'px, -100%)',
  transform: 'translate(' + snowLeft + 'px, ' + window.innerHeight + 'px)'
], 1500);

// less than 1500ms later...changed my mind
player.cancel();

【讨论】:

自 40 版(也可移动)起,Firefox 也支持网络动画:developer.mozilla.org/en-US/docs/Web/API/Animation。 ...但是浏览器支持仍然不足以用于 2017 年的生产,请参阅caniuse.com/#search=web%20animations

以上是关于以编程方式使用来自 JS 的 CSS 转换的干净方式?的主要内容,如果未能解决你的问题,请参考以下文章

如何以编程方式将 JS 和 CSS 资源添加到 <h:head>

MVC Razor,包含来自另一个项目的 JS/CSS 文件

将 CSS 对象转换为样式标签

多点连接 - 以编程方式断开对等点

使用带有 Ionic 的 3rd 方 CSS

如何以编程方式折叠或展开 SliverPresistentHeader [自定义浮动应用栏]