嵌套 css 缩放变换过渡不会相互抵消

Nested css scale transform transitions don't cancel out each other

我有一张图片会根据 overview/detail 状态更改其高度。它需要是带有 background-size: cover 的背景图像,以确保始终覆盖容器。

由于过渡需要高性能,我认为除了在包装元素上使用 transform: scaleY(0.5) 并在内部元素上使用相应的变换 transform: scaleY(2) 之外别无他法,即抵消比例效果。

开头和结尾的数学都是正确的。不过,过渡看起来是错误的。 我准备了一个小 fiddle 来说明问题所在:

Codepen - Fiddle

如您所见,过渡即使是线性的,也不会完全相互抵消。我想有一个潜在的数学问题,但我似乎找不到解决办法。

编辑:继续为贝塞尔曲线制作了一个示例代码笔。

http://codepen.io/anon/pen/jAYOBO <--(旧)

现在有过渡回正常元素状态的曲线:

http://codepen.io/anon/pen/oLpLLG <--(新)

您面临的问题是这些操作的相对性。您必须为游戏中的过渡赋予不同的值。您拥有关于转换的正确数学逻辑,但是,您拥有的正在修改这些特性的转换没有被调整以保持相同的动画,它们各自具有不同的值——转换值是相同的。

这意味着即使您在正在转换的两个对象之间有一个完美的 2:1 比例,它们的变化率在过程的不同步骤中本质上是不同的 - 无论他们的 destination/end 结果 。如果你想让它们在外观上均衡,你需要调整过渡设置。

还在迷茫?换句话说....

让我们假设您有 2 个 div 盒子,它们位于不同的容器中,彼此不影响。这里有一些 CSS 来传达它们的相关属性:

div.a{
width:100px;
height:100px;
}

div.b{
width:200px;
height:200px;
}

我们可以对它们进行完全相同的转换:线性、10 秒,并将它们各自的大小加倍 scale()。 (div.a 在 height/width 中为 200px,div.b 在 height/width 中为 400px)

现在,虽然它可能看起来与您的情况无关,而且很明显这两个不会 'grow' 以相同的速度 -(不同的开始,不同的目的地 [这并不同样否定初始开始的差异] ,持续时间相同)- 这与您遇到的问题相同。

要纠正该问题并让您的动画以您期望的方式运行,您必须修改过渡的计时函数。

为了证明我所描述的现象确实实际发生,我已经将其中一种转换元素的过渡类型更改为缓和并将其分叉到下面的 codepen link 上。玩它,你就会明白我在说什么。如果您需要更多信息,请评论我的回答!

http://codepen.io/anon/pen/qNVgBB

有帮助Link:http://cubic-bezier.com/

进一步阅读:Quadratic Bezier Curve: Calculate Point

(此外,我确信数学堆栈交换人员会 喜欢 为您仔细研究这些东西。我几乎肯定他们可以让您更清楚曲线如何工作的分解。) (呃,这现在太乱了。我会清理它并稍后重新格式化)

scaleY() 影响 parentchild 元素大小(例如 height)。

您正在以两种方式缩放 child 元素的 height同时增加和减少。您看到的完全是给定值的预期行为以及您为元素设置的属性。

假设如下:

  • parentchild 的高度都是 100px
  • scaleY parent 的数量是 0.5(这也会影响 childchild2.
  • transition时间是1000ms

500ms:

  • parent 应缩放为 0.75 (100px * 0.75 = 75px height)
  • child 应缩放为 1.5 (75px * 1.5 = 112.5px height)

1000ms:

  • parent 应缩放为 0.5 (100px * 0.5 = 50px height)
  • child 应缩放为 2) (50px * 2 = 100px height)

这就是您遇到 "grow then shrink" 行为的原因。

我不会花时间摆弄一些立方贝塞尔曲线值,而是责备您的元素结构和设计。

外部元素插值在 scaleY(1)scaleY(0.5) 之间。

在时间 t,转换将是 scaleY(1-t/2)

      t = 0                    t                     t = 1s
┌               ┐      ┌               ┐       ┌               ┐
│ 1   0   0   0 │      │ 1   0   0   0 │       │ 1   0   0   0 │
│ 0   1   0   0 │      │ 0 1-t/2 0   0 │       │ 0  1/2  0   0 │
│ 0   0   1   0 │      │ 0   0   1   0 │       │ 0   0   1   0 │
│ 0   0   0   1 │      │ 0   0   0   1 │       │ 0   0   0   1 │
└               ┘      └               ┘       └               ┘

内部元素插值在scaleY(1)scaleY(2)之间。

在时间 t,转换将是 scaleY(1+t)

      t = 0                    t                     t = 1s
┌               ┐      ┌               ┐       ┌               ┐
│ 1   0   0   0 │      │ 1   0   0   0 │       │ 1   0   0   0 │
│ 0   1   0   0 │      │ 0  1+t  0   0 │       │ 0   2   0   0 │
│ 0   0   1   0 │      │ 0   0   1   0 │       │ 0   0   1   0 │
│ 0   0   0   1 │      │ 0   0   0   1 │       │ 0   0   0   1 │
└               ┘      └               ┘       └               ┘

不过,那是相对于外在而言的。在绝对值中,矩阵相乘:

      t = 0                    t                     t = 1s
┌               ┐   ┌                      ┐   ┌               ┐
│ 1   0   0   0 │   │ 1     0      0     0 │   │ 1   0   0   0 │
│ 0  1*1  0   0 │   │ 0 1+t/2-t²/2 0     0 │   │ 0  2/2  0   0 │
│ 0   0   1   0 │   │ 0     0      1     0 │   │ 0   0   1   0 │
│ 0   0   0   1 │   │ 0     0      0     1 │   │ 0   0   0   1 │
└               ┘   └                      ┘   └               ┘

那么,是的,起点和终点对应单位矩阵。

但中间有抛物线 scaleY(1+t/2-t²/2)

使用贝塞尔曲线或许可以达到预期的效果。

f(t)g(t)分别为.outer.inner的计时函数。

根据定义,f(0) = g(0) = 0f(1) = g(1) = 1

外部的比例将由

给出
( 1-f(t)/2 ) ( 1+g(t) ) = 1 + g(t) - f(t)/2 - f(t)g(t)/2

我们希望它是 1,所以

f(t) = 2 g(t) / (1+g(t))
f'(t) = 2 g'(t) / (1+g(t))^2
f'(0) = 2 g'(0)
f'(1) = g'(1) / 2

也就是说,外层的起始坡度必须是内层的两倍,反之亦然。

选择f(t) = t(线性)和g (.3, 0.15), (.7, .4)给出的贝塞尔曲线似乎产生了不错的结果。注意 g'(0) = 2 = 2 f'(0)g'(1) = 1/2 = 1/2 f'(0).

.outer, .inner, .inner-expectation {
  transition: transform 2s;
  height: 400px;
}
.outer, .inner {
  transition-timing-function: linear;
}
.inner {
  transition-timing-function: cubic-bezier(.3, 0.15, .7, .4);
}
.inner {
  background-image: url(https://upload.wikimedia.org/wikipedia/commons/thumb/f/f4/Souq_Waqif%2C_Doha%2C_Catar%2C_2013-08-05%2C_DD_107.JPG/1920px-Souq_Waqif%2C_Doha%2C_Catar%2C_2013-08-05%2C_DD_107.JPG);
  background-size: cover;
}
a:hover ~ .outer {
  transform: scaleY(0.5);
}
a:hover ~ .outer .inner {
  transform: scaleY(2);
}
* {
  box-sizing: border-box;
}
a {
  display: block;
  position: absolute;
  top: 0;
  left: 200px;
  padding: 20px;
  background: wheat;
}
.text {
  position: absolute;
  top: 0;
  height: 100%;
}
.outer {
  background: #fcf8b3;
  position: absolute;
  top: 80px;
  left: 200px;
  width: 400px;
  height: 400px;
}
.inner, .inner-expectation {
  position: absolute;
  width: 300px;
  top: 0;
  left: 100px;
}
.inner .text, .inner-expectation .text {
  right: 0;
}
.inner-expectation {
  width: 20px;
  top: 80px;
  left: 610px;
  background: rgba(255, 0, 0, 0.5);
}
<a href="#">hover me</a>
<div class="outer">
  <div class="text">outer</div>
  <div class="inner">
    <div class="text">inner</div>
  </div>
</div>
<div class="inner-expectation"></div>

问题是当悬停结束时,效果就会中断。但这并不能完美解决。

反转时,外侧的刻度为(1+f(t))/2,内侧的刻度为2-g(t)(相对于外侧)

绝对而言,内部的规模将是

(1+f(t))/2 * (2-g(t)) = 1 - g(t)/2  + f(t) - f(t)g(t)/2

我们希望它成为 1。然后,f(t) = g(t) / ( 2-g(t) ).

但我们已经有 f(t) = 2 g(t) / (1+g(t)).

g(t) / ( 2-g(t) ) = 2 g(t) / (1+g(t))   =>   g(t) = 1

但是g(0)必须是0。矛盾。你不可能在两个方向上都达到完美的结果。

如果你愿意使用JS,你可以交换鼠标进入或离开目标元素时外部和内部的计时功能。