以编程方式从 JS 使用 CSS 转换的干净方法?

Clean way to programmatically use CSS transitions from JS?

提问人:Fabrício Matté 提问时间:9/2/2013 最后编辑:CommunityFabrício Matté 更新时间:10/28/2017 访问量:8660

问:

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

例如(小提琴):

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

这不会对 Chrome 29/Firefox 23 中元素的不透明度进行动画处理。这是因为(来源):

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

解决方案是在应用两组 性能。执行此操作的一种简单方法是访问 DOM 元素的属性 [...]offsetHeight

事实上,该黑客确实适用于当前的 Chrome/Firefox 版本。更新了代码(小提琴 - 打开小提琴后单击以再次运行动画):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设备上不起作用。

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

通过测试,我找到了另一个在最新的 Chrome 上运行良好的解决方案,使用 requestAnimationFramefiddle):

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

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

更新:经过进一步测试,该方法在 Firefox 23 上效果不佳 - 大多数时候似乎都失败了。(小提琴requestAnimationFrame)

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

JavaScript CSS CSS-Transitions 过渡

评论

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

答:

-1赞 Joon 9/2/2013 #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函数确保浏览器重绘

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

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

希望这有帮助!

评论

0赞 Fabrício Matté 9/4/2013
感谢您的输入。我做了一些修改,以便它可以多次对同一个元素进行动画处理: jsfiddle.net/eNCBz/7 虽然超时似乎非常小,但Firefox无法经常重绘(当要转换的页面/元素更复杂时,Chrome有时也会失败)。我认为 100 毫秒就足够了,但即便如此,也不是一个非常干净的解决方案(异步希望浏览器的魔力)。
3赞 James Dinsdale 1/22/2014 #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;}
}

此 JsFiddle 所示,它要么在页面加载(已添加类)中工作,要么在动态添加类以触发动画时工作。

0赞 Vandana Rajput 6/4/2015 #3

  1.   <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>

    #

15赞 Nickolay 8/7/2015 #4

目前还没有一种干净的方法(不使用CSS动画 - 有关使用CSS动画的示例,请参阅James Dinsdale的附近答案)。有一个规范错误 14617,不幸的是,自 2011 年提交以来没有采取行动。

setTimeout在 Firefox 中不能可靠地工作(这是设计使然)。

我不确定——对原始问题的编辑说它也不能可靠地工作,但我没有调查。(更新:看起来至少有一个Firefox核心开发人员认为是你可以进行更多更改的地方,不一定能看到之前更改的效果requestAnimationFramerequestAnimationFrame

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

window.getComputedStyle(elem).opacity;

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

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

评论

0赞 AxeEffect 11/6/2015
物业问这重要吗。它必须是 CSS 转换上使用的那个。如果没有,此技巧将不适用于 Chrome 或 Edge。但是,无论访问的属性如何,它都可以在Firefox上运行良好。
10赞 mik01aj 10/22/2015 #5

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

您可以使用 Web 动画。它们是在 Chrome 36 和 Firefox 40 中原生实现的,并且所有其他浏览器都有一个 polyfill

示例代码:

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();

评论

3赞 AxeEffect 11/10/2015
从版本 40 开始(也是移动版),Firefox 也支持 Web 动画:developer.mozilla.org/en-US/docs/Web/API/Animation
0赞 schellmax 12/1/2017
...但浏览器支持在 2017 年仍不足以用于生产,请参阅 caniuse.com/#search=web%20animations