如果元素开始隐藏,则 css 过渡不起作用

IT技术 javascript jquery css
2021-01-17 03:33:16

我有 2div秒,其中之一是通过display:none;. 两者在属性上都有相同的 css 过渡right

如果我更改属性right通过jQuery和显示被隐藏的div,无论是使用$.css('display','none')$.show()$.toggle()等,隐藏DIV的结束位置瞬间画

$('button').on('click',function(){
  $('.b').show();
  $('.b').css('right','80%');
  $('.a').css('right','80%');
})
body {
  width:800px;
  height:800px;
}

div {
  width:50px;
  height:50px;
  background-color:#333;
  position:absolute;
  display:none;
  right:5%;
  top:0;
  transition:right .5s cubic-bezier(0.645, 0.045, 0.355, 1);
  color: white;
}

.a {
  display:block;
  top:60px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class='a'>A
</div>
<div class='b'>B
</div>
<button>Launch</button>

如果我使用$.animate()它会起作用。但我的问题是;这是错误还是正常行为?

编辑不是显示上的过渡的重复:属性导致这里的问题不是动画显示属性也不是可见性

5个回答

要想看清情况,就需要了解CSSOM和DOM的关系。

之前的 Q/A 中,我对重绘过程的工作原理有了一些了解
基本上分为三个步骤,DOM 操作、回流和绘制。

  • 第一个(DOM 操作)只是修改一个 js 对象,并且都是同步的。
  • 第二个(回流,又名布局)是我们感兴趣的,并且稍微复杂一些,因为只有一些 DOM 方法和绘制操作需要它。它包括更新所有 CSS 规则并重新计算页面上每个元素的所有计算样式。
    作为一个相当复杂的操作,浏览器会尽量少做。
  • 第三个 ( paint ) 每秒最多只能执行 60 次(仅在需要时)。

CSS 转换通过从一种状态转换到另一种状态来工作。为此,他们查看元素最后计算值以创建初始状态。
由于浏览器仅在需要时才重新计算计算出的样式,因此在过渡开始时,您应用的所有 DOM 操作均无效。

所以在你的第一个场景中,当计算转换的初始状态时,我们有

.b { computedStyle: {display: none} }

......就是这样。

因为,是的,这display: none就是 CSSOM 的强大之处;如果一个元素有display: none,那么它不需要被绘制,它不存在。

所以我什至不确定转换算法是否会启动,但即使它启动,初始状态对于任何可转换值也是无效的,因为所有计算值都为空。

你的.a元素可见从一开始就没有这个问题,并且可以转变。

如果你能使其与延迟(诱导工作$.animate),这是因为DOM MANIP“这确实改变之间的display财产,这种延迟DOM MANIP的执行”,做触发转换,浏览器确实触发一个回流(例如,因为屏幕垂直同步在两者之间启动并且绘制操作被触发)。


现在,这不是问题的一部分,但既然我们确实更了解发生了什么,我们也可以更好地控制它

事实上,一些 DOM 方法确实需要有最新的计算值。举例来说Element.getBoundingClientRect,或element.offsetHeightgetComputedStyle(element).height等等。所有这些需要整个页面已经更新,以便拳击正确做出的计算值(例如一个元素可以有一个保证金推或多或少等)。

这意味着我们不必知道浏览器何时会触发回流,我们可以强制它在我们想要的时候这样做。

但是请记住,页面上的所有元素都需要更新,这不是一个小操作,如果浏览器手头宽大,这是有充分理由的。

所以最好偶尔使用它,每帧最多使用一次。

幸运的是,Web API 使我们能够在此绘制操作发生之前挂钩一些 js 代码:requestAnimationFrame

所以最好的办法是在这个 pre-paint 回调中只强制我们重排一次,并从这个回调中调用所有需要更新值的东西。

$('button').on('click',function(){
  $('.b').show(); // apply display:block synchronously
  
  requestAnimationFrame(() => { // wait just before the next paint
    document.body.offsetHeight; // force a reflow
    // trigger the transitions
    $('.b').css('right','80%');
    $('.a').css('right','80%');
  });
})
body {
  width:800px;
  height:800px;
}

div {
  width:50px;
  height:50px;
  background-color:#333;
  position:absolute;
  display:none;
  right:5%;
  top:0;
  transition:right .5s cubic-bezier(0.645, 0.045, 0.355, 1);
  color: white;
}

.a {
  display:block;
  top:60px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class='a'>A
</div>
<div class='b'>B
</div>
<button>Launch</button>

但说实话,设置起来并不总是那么容易,所以如果你确定它会偶尔被解雇,你可能会很懒惰并同步进行:

@Kaiido 也许我的 javascript 知识有点差,所以告诉我这是否可能是回流的原因:D
2021-03-18 03:33:16
@vincent-d 当持续时间为 时0我无法清楚地了解 jQuery 源中发生的情况,但是,是的,那里肯定有某些东西会触发回流。如果你有比我更多的时间来进行演练,入口就在这里
2021-03-25 03:33:16
好的,我将此标记为最佳。这个很有教益,谢谢!如果我正确理解了你的解释和 jquery 文档$.show(),@yunzen 是正确的,因为$.show()作为动画调用会在最后触发回流,对吗?( $.show(0))
2021-03-28 03:33:16
@Kaiido 再次感谢您,这非常有启发性和趣味性。我正在阅读 jquery 来解决这个问题,如果我找到了,我会告诉你的。无论如何,可以肯定的是,当您$.show()通过它传递参数时,会发生回流 ,因为这个评论Set the display of the elements in a second loop to avoid constant reflow所以我正在看的是全局变量,我找到了ownerDocument()appendChild()但我在您提供的列表中找不到它们gist .github.com/paulirish/5d52fb081b3570c81e3a
2021-04-07 03:33:16
哦, appendChild() 立即带有一个removeChild(). 每次调用时都会发生这种情况,show()但如果没有输入参数,则不会发生这种情况,因为它只会处理 display 属性(显然可以避免回流)
2021-04-09 03:33:16

show()没有参数的jQuery基本上是这样做的

$('.b').css('display', 'block');

所以你正在做的是这个

$('button').on('click',function(){
  $('.b').css('display', 'block');
  $('.b').css('right','80%');
  $('.a').css('right','80%');
})

这基本上与

$('button').on('click',function(){
  $('.b').css({
    'display': 'block',
    'right': '80%'
  });
  $('.a').css('right','80%');
})

但是,如果同时更改显示,则无法转换任何内容。

将持续时间值添加到show()调用中,比仅仅更改display. 它会将元素添加到这样的动画队列中

$('.b').css({
  'display': 'block', 
  'overflow': 'hidden',
  'height': '0',
  'width': '0',
  'margin': '0',
  'width': '0',
  'opacity': '0'
});
$('.b').animate({
  height: '50px',
  padding: '0px',
  margin: '0px', 
  width: '50px', 
  opacity: '1'
}, 0)

所以你需要做的是将0(零)的持续时间值show()作为参数放入调用中

$('button').on('click',function(){
  $('.b').show(0);
  $('.b').css('right','80%');
  $('.a').css('right','80%');
})
body {
  width:800px;
  height:800px;
}

div {
  width:50px;
  height:50px;
  background-color:#333;
  position:absolute;
  display:none;
  right:5%;
  top:0;
  transition:right .5s .1s cubic-bezier(0.645, 0.045, 0.355, 1);
  color: white;
}

.a {
  display:block;
  top:60px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class='a'>A
</div>
<div class='b'>B
</div>
<button>Launch</button>

有不同的方法调用......有和没有参数。外面的没有动画,有参数的那个是动画的……动画的正在解决这个问题
2021-03-21 03:33:16
@TemaniAfif 这就是文档所说的。它还说默认持续时间是400. 我糊涂了
2021-03-26 03:33:16
通过添加一个数字,您可以使其动画化并解决问题(任何数字都可以使用,甚至大于 0)
2021-03-28 03:33:16
这很奇怪,是的,但结果是完美的。在我回到这里之前(我以为我的帖子永远丢失了,哈哈)我想出了 show() 回调。是的,正如@TemaniAfif 解释的那样,它确实应用了 400 毫秒。所以我验证了这个解决方案,因为它是最好的 imho
2021-03-28 03:33:16
来自文档:The matched elements will be revealed immediately, with no animation. This is roughly equivalent to calling .css( "display", "block" ), api.jquery.com/show
2021-04-02 03:33:16

就个人而言,我会检查元素在显示后是否可见。然后继续通过jQuery。

像这样:https : //jsfiddle.net/789su6xb/

$('button').on('click',function(){

    var b = $('.b');
    b.show();

    if(b.is(":visible")) {
        $('.b').css('right','80%');
        $('.a').css('right','80%');
    }

});
我没有投反对票,但我明白为什么。我说我不想使用 $.animate 并让 css 过渡完成他的工作,所以如果我不想那样做,我也不需要一个必须检查 dom 状态的解决方案
2021-03-19 03:33:16
我不知道为什么你会得到一个反对票,你不应该得到它,你的答案仍然不够好,我告诉你原因。其他人得到了反对票,因为他们的回答完全是 100% 偏离主题
2021-03-20 03:33:16
请解释您的反对票。这是一个可以接受的答案。
2021-03-26 03:33:16
我的问题怎么不清楚?另外,检查验证答案,这是一个很好的答案,因为它是问题的解决方案,而不是获得类似“足够好”结果的偷偷摸摸的方法。
2021-03-31 03:33:16
你的问题很不清楚。每个人都因为您当前使用的代码提供改进的代码而受到反对。你问这是否是正常行为,是的。您的代码只需要像我们的答案之一一样编写。您需要声明该节目是即时的。提供延迟。或者检查您的元素是否可见。
2021-04-05 03:33:16

在放映时开始过渡的问题与尝试在加载时开始过渡的问题相同。CSS 不支持在元素显示的同一帧上启动动画,这真的很烦人。请参阅此处的答案:加载时的 css3 过渡动画?

解决方案是使用关键帧而不是过渡。但是你不能正确地做一个变量,它在 CSS 文件中被硬编码为 80%。

$('button').on('click',function(){
  $('.b').show();
  $('.b').addClass('goLeft');
  $('.a').addClass('goLeft');
})
body {
  width:800px;
  height:800px;
}

div {
  width:50px;
  height:50px;
  background-color:#333;
  position:absolute;
  display:none;
  right:5%;
  top:0;
  color: white;
}

.goLeft {
  animation-name: goLeft;
  animation-duration: .5s;
  animation-iteration-count: 1;
  animation-play-state: running;
  animation-fill-mode: forwards;
  animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1);
}

.a {
  display:block;
  top:60px;
}

@keyframes goLeft {
  from {
    right: 5%;
  }
  to {
    right: 80%;
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class='a'>A
</div>
<div class='b'>B
</div>
<button>Launch</button>

检查这个小提琴只需要添加一个超时

$('button').on('click',function(){
  $('.b').show();
  setTimeout(function(){ $('.b').css('right','80%');
  $('.a').css('right','80%'); }, 0);
  
})
body {
  width:800px;
  height:800px;
}

div {
  width:50px;
  height:50px;
  background-color:#333;
  position:absolute;
  display:none;
  right:5%;
  top:0;
  transition:right .5s cubic-bezier(0.645, 0.045, 0.355, 1);
}

.a {
  display:block;
  top:20%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class='a'>
</div>
<div class='b'>
</div>
<button>Launch</button>

https://jsfiddle.net/qu2pybch/

你可以在这里制作一个完整的工作片段。我为你做的
2021-03-27 03:33:16
谢谢。看起来很整洁
2021-04-04 03:33:16
@yunzen 感谢您的建议,下次会记住这一点。
2021-04-05 03:33:16
你不能把 codepen 的内容从那里粘贴到这里的片段中吗?
2021-04-11 03:33:16