【问题标题】:Trigger and reverse CSS transition with Javascript使用 Javascript 触发和反转 CSS 过渡
【发布时间】:2020-05-12 20:48:01
【问题描述】:

我目前正在探索 FLIP technique 的可能性,它将所有 CSS 转换仅减少到 transformopacity(因为 GPU 加速)。它涉及直接使用 Javascript 操作样式。虽然触发这样的转变并不难,但我发现自己无法扭转它。通常是在 CSS 中定义的过渡,例如当您停止悬停时,悬停会自动反转。但是 CSS 不足以触发(和反向)点击转换。我希望能够做到以下几点:

a) 点击一个项目(并通过例如 CSS 类切换触发大小更改)
b) 计算其初始大小和新大小之间的差异,并触发 transformtransition
c) 在它改变大小时再次单击它
d) 从点击时的位置反转过渡

我的问题在于 d) 步骤。由于某种原因,元素突然将其大小更改为既不是旧大小也不是新大小,而是完全不同的大小。我有一个我在这里尝试做的例子:

https://codesandbox.io/s/flamboyant-snowflake-47t59

单击一个正方形,然后在它放大时再次单击它。

有什么可靠的方法可以正确地做我想做的事吗?有没有好的替代品?

【问题讨论】:

    标签: javascript html css animation css-transitions


    【解决方案1】:

    我相信这就是您正在寻找的。另外,在我看来,您似乎不太了解requestAnimationFrame。它是为了替换一种涉及递归调用 setInterval 以每隔几毫秒更改元素的 CSS 属性的技术(阅读更多 here)。所以我建议先阅读更多关于requestAnimationFrame的内容。

    我想指出,使用 CSS 过渡可以更轻松地制作下面的动画。此外,MDN 提到两者都具有或多或少相似的性能(阅读here)。不过,这里有一个你想要的最小工作示例:

    let clicked = false;
    let square = document.querySelector(".small-sq");
    let expandID
    let retractID
    
    square.style.transformOrigin = 'top left'
    square.style.transform = 'scale(1)'
    square.addEventListener('click', e => {
      let timeStart = new Date()
      let currentScale = parseFloat(square.style.transform.slice(6, -1))
      if (!clicked) {
        cancelAnimationFrame(retractID)
        expandID = requestAnimationFrame(() => repeat(timeStart, currentScale, clicked))
      }
      else {
        cancelAnimationFrame(expandID)
        retractID = requestAnimationFrame(() => repeat(timeStart, currentScale, clicked))
      }
      clicked = !clicked
    })
    
    function repeat(timeStart, currentScale, status) {
        let timeDifference = new Date() - timeStart
        if (status) {
          let scaleValue = currentScale + (1 * timeDifference / 5000) > 2 ? 2 : currentScale + (1 * timeDifference / 5000)
          square.style.transform = `scale(${scaleValue})`
          expandID = requestAnimationFrame(() => repeat(timeStart, scaleValue, status))
          if (scaleValue >= 2)
            cancelAnimationFrame(expandID)
        }
        else {
          let scaleValue = currentScale - (1 * timeDifference / 5000) < 1 ? 1 : currentScale - (1 * timeDifference / 5000)
          square.style.transform = `scale(${scaleValue})`
          retractID = requestAnimationFrame(() => repeat(timeStart, scaleValue, status))
          if (scaleValue <= 1)
            cancelAnimationFrame(retractID)
        }
        
      }
    .small-sq {
      height: 100px;
      width: 100px;
      background-color: rgb(131, 42, 131);
    }
    .big-sq {
      height: 500px;
      width: 500px;
      background-color: rgb(131, 42, 131);
    }
    <div id="app">
      <div class="small-sq"></div>
    </div>

    如果你使用 CSS 过渡,那么它可以转换成如下:

    let square = document.querySelector('.small-sq')
    
    square.addEventListener('click', e => {
      square.classList.toggle('toggled')
    })
    .small-sq {
      height: 100px;
      width: 100px;
      background-color: rgb(131, 42, 131);
      transform-origin: left top;
      transition: transform 1s linear;
    }
    .small-sq.toggled {
      transform: scale(2)
    }
    <div id="app">
      <div class="small-sq"></div>
    </div>

    【讨论】:

    • 感谢您的详细解答。我知道性能,我认为您的第二个示例可以在很多情况下使用。但在某些情况下,这还不够(动画 DOM 位置更改,例如列表混洗或共享元素转换)。
    【解决方案2】:

    我设法找到了一个可行的解决方案(它导入了rematrix 库以使转换矩阵的计算更容易)。这个解决方案和 Richard 提出的更简单的解决方案都能够制作 60fps 动画 - 我想区别在于每种方法可以处理的案例数量。

    https://codesandbox.io/s/working-transform-reverse-urc16

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-21
      相关资源
      最近更新 更多