如何使用变换动画高度而不压缩内容

huangapple go评论73阅读模式
英文:

How to animate height with transform without squishing content

问题

在我的项目中,我一直在使用 jQuery 的 slideUp() 来在用户点击按钮时将列表中的一个元素向上滑动,但众所周知,动画化 CSS 的高度需要重新布局,使动画显得卡顿。这个动画是应用程序的一个重要部分,我愿意付出大量工作来使其运行并且运行流畅。

我已经决定使用 CSS 的 transform 来使其运行流畅,因为它在 GPU 上处理,而且在一些现代浏览器上,甚至不在主线程上运行,重量级的 JS 工作也不会影响变换。(我确实有大量的 JS 工作)。

我正在寻找一个巧妙的解决方案,使用 CSS 的 transition: transform 来复制 jQuery 的 slideUp,它只是对 height 属性进行动画处理。下面是我的尝试,但似乎 scaletranslate 的同步效果不如预期。

$("button").on("click", function() {
  $(".collapsible").addClass("collapsed")
  setTimeout(function() {
    $(".collapsible").removeClass("collapsed")
  }, 5000);
});
.list-item {
  width: 400px;
  height: 100px;
  background-color: blue;
  margin-top: 10px;
  overflow-y: hidden;
}

.collapsible.collapsed .content {
  transition: transform 3s linear;
  transform: scale(1, 1) translate(0, -100%);
}

.collapsible.collapsed {
  transition: transform 3s linear;
  transform: translate(0, -50%) scale(1, 0);
}

.collapsible.collapsed ~ .list-item {
  transition: transform 3s linear;
  transform: translate(0, -100%);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
<button>Collapse</button>
<div class="list-item collapsible">
  <div class="content">
    Just an example <br>
    Just an example <br>
    Just an example <br>
    Just an example <br>
    Just an example <br>
  </div>
</div>
<div class="list-item">
  
</div>
<div class="list-item">
  
</div>

我调整了一些数值,并通过将 content 的变换改为 transform: scale(1, 3) translate(0, -50%); 来使其更接近。

似乎我离成功很近了,但从未真正成功。是否有任何明确的技巧可以做到这一点?

要求:

  • 最好不用 JS
  • 不在主线程上运行
英文:

In my project I have been using jQuery slideUp() to slide up an element in a 200 item list when the user clicks a button. But, as everyone knows, animating CSS height requires a reflow, making the animation jerky. This animation is an integral part of the application and I am willing to go to extensive work to make it work AND work smoothly.

I have decided that CSS transform is the way to make it work smoothly because of the fact that it is handled on the gpu and on some modern browsers, it is even off the main thread and heavy JS work won't affect the transform. (I do have heavy JS work).

I am looking for a clever solution with CSS transition: transform to replicate jQuery slideUp, which just animates the height property. Below was my attempt, but it seems scale and translate do not sync as expected.

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

$(&quot;button&quot;).on(&quot;click&quot;, function() {
  $(&quot;.collapsible&quot;).addClass(&quot;collapsed&quot;)
  setTimeout(function() {
    $(&quot;.collapsible&quot;).removeClass(&quot;collapsed&quot;)
  }, 5000);
});

<!-- language: lang-css -->

.list-item {
  width: 400px;
  height: 100px;
  background-color: blue;
  margin-top: 10px;
  overflow-y: hidden;
}

.collapsible.collapsed .content {
  transition: transform 3s linear;
  transform: scale(1, 1) translate(0, -100%);
}

.collapsible.collapsed {
  transition: transform 3s linear;
  transform: translate(0, -50%) scale(1, 0);
}

.collapsible.collapsed ~ .list-item {
  transition: transform 3s linear;
  transform: translate(0, -100%);
}

<!-- language: lang-html -->

&lt;script src=&quot;https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js&quot;&gt;&lt;/script&gt;
&lt;button&gt;Collapse&lt;/button&gt;
&lt;div class=&quot;list-item collapsible&quot;&gt;
  &lt;div class=&quot;content&quot;&gt;
    Just an example &lt;br&gt;
    Just an example &lt;br&gt;
    Just an example &lt;br&gt;
    Just an example &lt;br&gt;
    Just an example &lt;br&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;list-item&quot;&gt;
  
&lt;/div&gt;
&lt;div class=&quot;list-item&quot;&gt;
  
&lt;/div&gt;

<!-- end snippet -->

I played with the values some and got it closer by changing the content transform to transform: scale(1, 3) translate(0, -50%);.

It seems I am so close to achieving, but never quite succeeding. Is there any cut and dried trick out there for this?

Requirements:

  • Preferably no JS
  • Off the main thread

答案1

得分: 2

在几个夜晚的熬夜后,我找到了一个完全符合我需求的解决方案。我必须利用三层 divs,并确保内部的两层设置为 height: 100%;。然后我放弃了 scale(),使用 transform() 将中间层向上滑动。

这满足了我的要求:

  • 使用了在 GPU 上以最高 60fps 运行的 Transform()
  • 不会用 Scale() 压缩内容
  • 内容从底部向顶部消失,就像 jQuery 的 SlideUp() 一样
  • 下方的其他元素会上移以填补空白,无论目标元素的高度如何
  • 一旦触发动画,就不再涉及 JS
$("button").on("click", function() {
  const height = $(".collapsible").css("height");
  $("root").css("--height", height);
  $(".collapsible").addClass("collapsed");
  setTimeout(function() {
    $(".collapsible").removeClass("collapsed");
  }, 4000);
});
:root {
  --height: unset;
}

.outer-container {
  width: 400px;
  height: fit-content;
  background-color: transparent;
  margin-top: 10px;
  overflow-y: hidden;
}

.inner-container {
  height: 100%;
  background-color: blue;
  overflow: hidden;
}

.content {
  height: 100%;
}

.collapsible.collapsed .inner-container {
  transition: transform 3s linear;
  transform: translate(0, -100%);
}

.collapsible.collapsed .content {
  transition: transform 3s linear;
  transform: translate(0, 100%);
}

.collapsible.collapsed ~ .outer-container {
  transition: transform 3s linear;
  transform: translate(0, calc((var(--height) * -1) - 10px));
}

.collapsible .inner-container {
  transition: transform .5s ease-in-out;
  transform: translate(0, 0);
}

.collapsible .content {
  transition: transform .5s ease-in-out;
  transform: translate(0, 0);
}

.collapsible ~ .outer-container {
  transition: transform .5s ease-in-out;
  transform: translate(0, 0);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
<button>Collapse</button>
<div class="outer-container">
  <div class="inner-container">
    <div class="content">
      Just an example <br>
      Just an example <br>
      Just an example <br>
      Just an example <br>
    </div>
  </div>
</div>
<div class="outer-container collapsible">
  <div class="inner-container">
    <div class="content">
      Just an example <br>
      Just an example <br>
      Just an example <br>
      Just an example <br>
      Just an example <br>
    </div>
  </div>
</div>
<div class="outer-container">
  <div class="inner-container">
    <div class="content">
      Just an example <br>
      Just an example <br>
      Just an example <br>
    </div>
  </div>
</div>
<div class="outer-container">
  <div class="inner-container">
    <div class="content">
      Just an example <br>
      Just an example <br>
      Just an example <br>
      Just an example <br>
    </div>
  </div>
</div>
英文:

With a few nights of sleep, I landed on a solution that works precisely as I needed. I had to utilize three layers of divs and make sure the inner two are set to height: 100%;. I then do away with scale() and use transform() to slide the middle layer up.

This meets my requirements:

  • Uses Transform() which should run on the GPU up to 60fps
  • Doesn't squish content with Scale()
  • Content disappears from bottom to top like jQuery SlideUp()
  • Other elements below slide up to fill the gap no matter the height of the target element
  • Once animation is triggered there is no more JS involved

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

$(&quot;button&quot;).on(&quot;click&quot;, function() {
  const height = $(&quot;.collapsible&quot;).css(&quot;height&quot;);
  $(&quot;:root&quot;).css(&quot;--height&quot;, height);
  $(&quot;.collapsible&quot;).addClass(&quot;collapsed&quot;);
  setTimeout(function() {
    $(&quot;.collapsible&quot;).removeClass(&quot;collapsed&quot;);
  }, 4000);
});

<!-- language: lang-css -->

:root {
  --height: unset;
}

.outer-container {
  width: 400px;
  height: fit-content;
  background-color: transparent;
  margin-top: 10px;
  overflow-y: hidden;
}

.inner-container {
  height: 100%;
  background-color: blue;
  overflow: hidden;
}

.content {
  height: 100%;
}

.collapsible.collapsed .inner-container {
  transition: transform 3s linear;
  transform: translate(0, -100%);
}

.collapsible.collapsed .content {
  transition: transform 3s linear;
  transform: translate(0, 100%);
}

.collapsible.collapsed ~ .outer-container {
  transition: transform 3s linear;
  transform: translate(0, calc((var(--height) * -1) - 10px));
}

.collapsible .inner-container {
  transition: transform .5s ease-in-out;
  transform: translate(0, 0);
}

.collapsible .content {
  transition: transform .5s ease-in-out;
  transform: translate(0, 0);
}

.collapsible ~ .outer-container {
  transition: transform .5s ease-in-out;
  transform: translate(0, 0);
}

<!-- language: lang-html -->

&lt;script src=&quot;https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js&quot;&gt;&lt;/script&gt;
&lt;button&gt;Collapse&lt;/button&gt;
&lt;div class=&quot;outer-container&quot;&gt;
  &lt;div class=&quot;inner-container&quot;&gt;
    &lt;div class=&quot;content&quot;&gt;
      Just an example &lt;br&gt;
      Just an example &lt;br&gt;
      Just an example &lt;br&gt;
      Just an example &lt;br&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;outer-container collapsible&quot;&gt;
  &lt;div class=&quot;inner-container&quot;&gt;
    &lt;div class=&quot;content&quot;&gt;
      Just an example &lt;br&gt;
      Just an example &lt;br&gt;
      Just an example &lt;br&gt;
      Just an example &lt;br&gt;
      Just an example &lt;br&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;outer-container&quot;&gt;
  &lt;div class=&quot;inner-container&quot;&gt;
    &lt;div class=&quot;content&quot;&gt;
      Just an example &lt;br&gt;
      Just an example &lt;br&gt;
      Just an example &lt;br&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;outer-container&quot;&gt;
  &lt;div class=&quot;inner-container&quot;&gt;
    &lt;div class=&quot;content&quot;&gt;
      Just an example &lt;br&gt;
      Just an example &lt;br&gt;
      Just an example &lt;br&gt;
      Just an example &lt;br&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

<!-- end snippet -->

Note: for my particular use case, I made the following div slide up far enough to cover the margin. I believe this is how a jQuery slideUp() function works.

EDIT: Updated snippet so it works with items of different heights by using a css variable and setting it with JS.

huangapple
  • 本文由 发表于 2023年2月19日 05:36:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/75496535.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定