【问题标题】:Nested css scale transform transitions don't cancel out each other嵌套的 css 比例变换过渡不会相互抵消
【发布时间】:2016-07-16 12:43:46
【问题描述】:

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

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

在开始和结束时,数学都是正确的。不过,过渡看起来是错误的。 我准备了一个小小提琴来说明出了什么问题:

Codepen - Fiddle

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

【问题讨论】:

  • 你是两个线性函数相乘,所以预期结果是抛物线。

标签: css css-transforms


【解决方案1】:

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

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

现在带有转换回正常元素状态的曲线:

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

您面临的问题是这些操作的相对性。您必须为播放中的过渡提供不同的值。您拥有关于变换的数学逻辑,但是,您修改这些特征的变换不会被调整以保持具有各自不同值的相同动画 - 变换值是相同的。

这意味着即使您在被转换的两个对象之间有一个完美的 2:1 比例,它们的变化率在过程的不同步骤中本质上是不同的 - 无论它们的目标/最终结果如何。如果您希望它们在外观上均衡,则需要调整过渡设置。

还在困惑吗?也就是说……

假设您有 2 个 div 框,它们位于不相互影响的独立容器中。下面是一些 CSS 来传达它们的相关属性:

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

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

我们可以对这些完全相同的元素进行转换:线性、10s,并将它们各自的大小加倍scale()。 (div.a 的高度/宽度为 200 像素,div.b 的高度/宽度为 400 像素)

现在,虽然这似乎与您的情况无关,并且很明显这两个不会以相同的速度“增长” - (不同的开始,不同的目的地 [不同样否定初始开始的差异],具有相同的持续时间) - 这与您遇到的问题相同。

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

为了证明我所描述的现象确实是实际发生的,我继续将转换元素之一的转换类型更改为缓和并在下面的codepen链接。玩它,你会明白我在说什么。如果您需要更多信息,请评论我的回答!

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

有用的链接:http://cubic-bezier.com/

延伸阅读:Quadratic Bezier Curve: Calculate Point

(另外,我相信数学堆栈交换人员会喜欢为你咀嚼这些东西。我几乎肯定他们可以让你更清楚地了解曲线是如何分解的工作。) (呃,这现在真是一团糟。我会清理它并稍后重新格式化)

【讨论】:

  • 非常感谢您的解释。这意味着,我走在正确的轨道上 :) 你知道我如何计算一个可以抵消外部函数的三次贝塞尔函数吗?我会保留试错法作为最后的手段。
  • 我认为您的结构存在固有问题;听起来您可能会更轻松地使用transitioning 与box model 相关的其他属性,例如widthheight。但是,如果您仍想继续此操作,我相信我可以提出一个示例贝塞尔曲线,这将帮助您完成 scaleY() 取消工作!完成后,我将编辑答案并在顶部扔一个 codepen。
  • 太棒了!这绝对值得接受作为答案。我能问一下你是如何得到曲线函数中的数字的吗?我想进一步了解随机比例因子,但我不完全确定公式是什么。
  • 我很抱歉,但我无法为您提供此类事情的公式。它不仅受环境影响,而且更适合在课堂环境中教授。也许评论很难用于这种事情。我将添加一些进一步的阅读链接,一个帮助可视化曲线的工具,以及你的 codepen 的反面,这样你就可以从玩它中学到更多。此外,如果您将来需要曲线方面的帮助,请随时与我联系。
  • 还是谢谢你,非常感谢所有帮助!
【解决方案2】:

外部元素在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,你可以在鼠标进入或离开目标元素时交换外层和内层的计时功能。

【讨论】:

  • 非常感谢!这对我理解问题有很大帮助。我在你的数学技能面前低头。是否可以用可以附加到内部元素的三次贝塞尔函数来表达您的公式?
  • @Nirazul 我对贝塞尔曲线知之甚少,但我的初步直觉表明不可能将内部元素稳定在比例 1。但您可能会接近。
  • @Nirazul 看来我设法得到了一个看似完美的结果。但只有前锋。向前和向后都是不可能的。
  • 无论如何,这是一个非常有趣的问题:)
【解决方案3】:

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

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

假设如下:

  • parentchild 在 `height 中都是 100px
  • scaleYparent 的数量是 0.5(这也会影响 child 并且 child2
  • 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)

这就是为什么您会遇到 “先增长后缩小” 的行为。

我不会花时间摆弄一些三次贝塞尔值,而是责备你的元素结构和设计。

【讨论】:

  • 感谢您的解释!我知道这样的问题可能很耗时,所以我同意重组可能是最好的。但有时这是不可能的,因为这是一个纯粹的数学问题,我敢肯定有一个解决方案:)
猜你喜欢
  • 1970-01-01
  • 2014-08-08
  • 1970-01-01
  • 1970-01-01
  • 2018-01-14
  • 1970-01-01
  • 2015-07-15
  • 2016-12-04
  • 1970-01-01
相关资源
最近更新 更多