暂时绕过 CSS 过渡
Posted
技术标签:
【中文标题】暂时绕过 CSS 过渡【英文标题】:Temporarily bypass a CSS transition 【发布时间】:2016-04-16 00:49:53 【问题描述】:假设我有这样的风格,有一个过渡:
#theElement
position: relative;
left: 200px;
transition: left 1s;
还有一些代码:
var element = document.getElementById("theElement");
function animateX(px)
element.style.left = px + "px";
所以,只有一个 animateX
函数,它简单地说明它的作用,动画 theElement
的左侧属性,现在如果我也想要一个立即设置的函数left 属性,没有过渡:
function setX(px)
element.style.transition = "none";
element.style.left = px + "px";
element.style.transition = "left 1s";
为什么这不起作用,我该如何解决?
【问题讨论】:
【参考方案1】:为了完成这项工作,您需要通过在 JS 中读取 CSS 更改来刷新它们。这个问题的答案解释了它是如何工作的:
Can I use javascript to force the browser to "flush" any pending layout changes?
下面的工作示例:
var element = document.getElementById("theElement");
function animateX(px)
element.style.left = px + "px";
function setX(px)
element.style.transition = "none";
element.style.left = px + "px";
// apply the "transition: none" and "left: Xpx" rule immediately
flushCss(element);
// restore animation
element.style.transition = "";
function flushCss(element)
// By reading the offsetHeight property, we are forcing
// the browser to flush the pending CSS changes (which it
// does to ensure the value obtained is accurate).
element.offsetHeight;
#theElement
position: relative;
left: 200px;
transition: left 1s;
width: 50px;
height: 50px;
background: red;
.wrapper
width: 400px;
height: 100px;
<div class="wrapper">
<div id="theElement"></div>
</div>
<button onclick="animateX(100)">Animate 100</button>
<button onclick="setX(0)">Set 0</button>
【讨论】:
你真的需要刷两次,因为lex82的回答只需要一次超时吗? 没有。我认为通过显示两次同花会更容易理解,但我想它只会让它更加混乱。我已经编辑了我的答案来证明这一点。 正如我的回答中所解释的,您不应通过在元素上明确设置动画来恢复动画。您将覆盖样式表(就像您在抑制动画时所做的那样)。 好答案。我的版本是在 setTimeout 中完成后半部分,但这更好。 使用“offsetHeight”强制应用属性的非常有趣的方式 - 感谢您的洞察力!【参考方案2】:其他解决方案很好,但我想提供一个替代方案并解释正在发生的事情。
在您的示例中无法按预期工作的原因是所有 CSS 属性都已更改,然后触发 browser reflow event 并开始转换。换句话说,transition
属性设置为none
,然后left
属性更改,然后transition
属性更改回@ 987654328@.
在所有样式属性更新后,触发浏览器重排事件,重新绘制 CSS,然后开始过渡:
element.style.transition = "none";
element.style.left = px + "px";
element.style.transition = "left 1s";
// The transition starts after all the CSS has been modified and a reflow has been trigged.
这样执行有几个原因。
主要原因是性能。与 repainting 更改单个 CSS 属性后的每个元素相比,更改所有属性并具有单个重绘事件更有效。此外,如果您要转换多个属性,您会希望在转换元素之前更改每个属性(这正是正在发生的事情)。
如果您想要一个不涉及任何超时/延迟或强制回流的干净替代方案,您可以在设置值之前将transitionDuration
属性设置为0s
,然后在转换元素时删除此内联样式.
例如:
var element = document.getElementById("theElement");
function setX(px)
element.style.transitionDuration = '0s';
element.style.left = px + "px";
function animateX(px)
element.style.transitionDuration = '';
element.style.left = px + "px";
#theElement
position: relative;
width: 100px;
height: 100px;
border: 2px solid;
left: 200px;
transition: left 1s;
<div id="theElement"></div>
<button onclick="animateX(100)">Transition 100</button>
<button onclick="animateX(300)">Transition 300</button>
<button onclick="setX(0)">Set 0</button>
<button onclick="setX(50)">Set 50</button>
同样,您也可以在转换或设置值之前切换元素上的类:
var element = document.getElementById("theElement");
function setX(px)
element.classList.remove('transition');
element.style.left = px + "px";
function animateX(px)
element.classList.add('transition');
element.style.left = px + "px";
#theElement
position: relative;
width: 100px;
height: 100px;
border: 2px solid;
left: 200px;
#theElement.transition
transition: left 1s;
<div id="theElement"></div>
<button onclick="animateX(100)">Transition 100</button>
<button onclick="animateX(300)">Transition 300</button>
<button onclick="setX(0)">Set 0</button>
<button onclick="setX(50)">Set 50</button>
上面的 sn-p 可以工作,但也值得指出的是,你可以监听transitionend
事件并在过渡结束时删除类:
例如:
function animateX(px)
element.classList.add('transition');
element.style.left = px + "px";
element.addEventListener('transitionend', callback);
function callback()
this.classList.remove('transition');
this.removeEventListener('transitionend', callback);
【讨论】:
很好的答案。我不知道你可以收听transitionend
事件。本次活动是否有良好的浏览器支持?
@jperezov 是的,它有good browser support。所有现代浏览器都支持它,但您可能需要为旧浏览器使用前缀版本,例如webkitTransitionEnd
、msTransitionEnd
。【参考方案3】:
您可以这样做,但您必须通过超时恢复旧的转换属性。
function setX(px)
element.style.transition = "none";
element.style.left = px + "px";
setTimeout(function()
element.style.transition = "left 1s";
);
JSFiddle
如果你不使用超时来恢复旧的转换属性,浏览器将永远不会知道它已经改变了。除非您的代码完全执行,否则它不会呈现页面。
我还建议像这样取消设置过渡属性:
element.style.transition = "";
否则,您将永远覆盖此元素的样式表。在元素上设置过渡样式之前,过渡由样式表确定。如果在元素上设置过渡样式,则会覆盖样式表。
更新:似乎超时时间不够。我在 FF/Linux 中尝试过,但没有成功。因此,您要么添加更多毫秒(不推荐),要么应用@jperezov 的解决方案(推荐)。
【讨论】:
另外,setTimeout 不需要第二个参数来表示毫秒吗? 它没有。默认为零,因此几乎没有延迟。 永远覆盖样式表是什么意思,能举个例子吗? 在不知道幕后发生的事情的情况下推荐 setTimeout 不是一个好主意,这在 Josh Crozier 的回答中得到了更好的解释。【参考方案4】:只是为了对 Josh Crozier 的回答进行充分且充分解释的补充:
还有ontransitioncalcel事件,当事件被取消时触发,所以你可以测试一些方法是否取消它。来自current draft我cherrypick:
如果一个元素不再在文档中,实现必须取消任何正在运行的转换
这样您就可以删除元素,进行更改,然后将元素添加回原来的位置;设置display: none
有各种副作用,停止它们之间的转换,
如果元素具有属性的运行转换,并且没有匹配的转换属性值,则实现必须取消正在运行的转换
因此您可以尝试删除转换属性值,
如果正在运行的过渡中属性的当前值等于更改后样式中的属性值,或者如果这两个值不可过渡,则实现必须取消正在运行的过渡。
因此您可以先将 left 属性设置为 auto
或 initial
以中断过渡。
唉,没有任何过渡事件是可取消的,因此在其中调用 e.preventDefault()
不应阻止它们。
【讨论】:
以上是关于暂时绕过 CSS 过渡的主要内容,如果未能解决你的问题,请参考以下文章