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

Posted

技术标签:

【中文标题】如果元素开始隐藏,css 过渡不起作用【英文标题】:css transition doesn't work if element start hidden 【发布时间】:2019-06-18 02:11:27 【问题描述】:

我有 2 个divs,其中一个通过display:none; 隐藏。两者在属性 right 上具有相同的 css 转换。

如果我通过 JQuery 更改属性 right 并显示隐藏的 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(),它将起作用。但我的问题是;这是错误还是正常行为?

编辑不是Transitions on the display: property的重复项,因为这里的问题不在于动画显示属性也不在于可见性

【问题讨论】:

正常行为,在显示和更改右侧之间需要稍许延迟 我正要说“好的”,但你这里的正常行为是什么?不起作用的东西不能是正常的行为,对吗?这只是一个错误,一个众所周知的错误,我可以理解,我会处理它,但仍然是一个错误,不是吗? 这不是重复的。我怎么能否认呢? 这不是一个错误,但它应该是这样工作的......是的,它不是重复的。我没有必要的语言来解释它,但其他人会来更好地解释 检查这个:jsfiddle.net/r7016skg 添加一个小的延迟将输出你所期望的 【参考方案1】:

要想清楚地理解这种情况,你需要了解 CSSOM 和 DOM 之间的关系。

在previous Q/A 中,我对重绘 过程的工作原理做了一些说明。 基本上,有三个步骤,DOM 操作、回流和绘制。

第一个(DOM操作)只是修改一个js对象,都是同步的。 第二个(reflow,又名 layout)是我们感兴趣的,而且有点复杂,因为只有一些 DOM 方法和绘制操作需要它.它包括更新所有 CSS 规则并重新计算页面上每个元素的所有计算样式。 作为一个相当复杂的操作,浏览器会尽可能少地尝试这样做。 第三次 (paint) 每秒最多只能完成 60 次(仅在需要时)。

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

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

.b  computedStyle: display: none 

...就是这样。

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

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

您的.a 元素从一开始就可见,不存在此问题,可以进行转换。

如果您能够使其延迟工作(由 $.animate 引起),那是因为在确实更改了 display 属性的 DOM manip' 和确实触发的延迟的 DOM manip' 执行之间过渡时,浏览器确实触发了 reflow(例如,因为屏幕垂直同步在两者之间启动,并且触发了 paint 操作)。


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

确实,some DOM methods 确实需要有最新的计算值。例如Element.getBoundingClientRectelement.offsetHeightgetComputedStyle(element).height 等。所有这些都需要整个页面更新计算值,以便正确进行装箱(例如,元素可以有边距或多或少地推动它,等等)。

这意味着我们不必不知道浏览器何时会触发这个reflow,我们可以在需要的时候强制它去做。

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

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

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

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

$('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>

但老实说,设置起来并不总是那么容易,所以如果你确定它是偶尔会被触发的东西,你可能会懒惰并同步进行:

$('button').on('click',function()
  $('.b').show(); // apply display:block
  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>

【讨论】:

好的,我将其标记为最佳。这很有指导意义,谢谢!如果我正确理解您解释的内容和$.show() 的jquery 文档,@yunzen 是正确的,因为调用$.show() 作为动画将在最后触发重排,对吗? ($.show(0)) @vincent-d 当持续时间为0 时,我无法清楚地了解 jQuery 源代码中发生的情况,但是是的,肯定有一些东西会触发回流。如果你有比我更多的时间来完成演练,入口is here。 @Kaiido 再次感谢你,这很有启发性和趣味性。我正在阅读 jquery 来解决这个问题,如果我找到了,我会告诉你。无论如何,当你使用$.show() 传递参数时,肯定会有回流,因为这个评论Set the display of the elements in a second loop to avoid constant reflow 所以我正在看的是全局变量,我发现ownerDocument()appendChild()但是我在您在此处提供的列表中找不到它们gist.github.com/paulirish/5d52fb081b3570c81e3a 哦,appenChild() 立即带有removeChild()。每次调用 show() 时都会发生这种情况,但如果您没有输入参数,则不会发生这种情况,因为它只会与 display 属性打交道(显然可以避免回流) @Kaiido 也许我的javascript知识有点差,所以告诉我这是否可能是回流的原因:D【参考方案2】:

不带参数的jQueryshow()基本上就是这样做的

$('.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>

【讨论】:

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

我个人会检查元素在显示后是否可见。然后继续通过 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 状态的解决方案 你的问题很不清楚。每个人都因为为您当前使用的代码提供改进的代码而遭到反对。你问这是否是正常行为,是的。您的代码只需要像我们的答案之一那样编写。您需要声明该节目是即时的。提供延迟。或者检查你的元素是否可见。 我的问题怎么不清楚?另外,检查验证答案,这是一个很好的答案,因为它是问题的解决方案,而不是获得类似“足够好”结果的偷偷摸摸的方法。 我不知道为什么你会被否决,你不应该得到它,但你的答案还不足以成为好的答案,我告诉你为什么。其他人被否决了,因为他们的回答完全 100% 偏离主题【参考方案4】:

在节目中开始过渡的问题与尝试在加载时开始过渡的问题相同。 CSS 不支持在元素显示的同一帧上启动动画,这真的很烦人。在这里查看答案:css3 transition animation on load?

解决方案是使用关键帧而不是过渡。但是你不能正确地做一个变量,它在 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>

【讨论】:

【参考方案5】:

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

$('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/

【讨论】:

你不能把那里的codepen内容粘贴到这里的sn-p吗? @yunzen 感谢您的建议,下次会记住这一点。 你可以在这里制作一个完整的工作 sn-p。我为你做到了 谢谢。看起来很整洁

以上是关于如果元素开始隐藏,css 过渡不起作用的主要内容,如果未能解决你的问题,请参考以下文章

悬停时不透明度的 CSS3 过渡不起作用

a:hover 的不透明度过渡不起作用

通过 JavaScript 分配 CSS 转换时不起作用

VueJS过渡“进入”不起作用

CSS显示:表格单元格,设置高度和溢出:隐藏,不起作用?

CSS:在悬停时替换文本,但平滑过渡到新文本不起作用?