什么?你还不懂transform这些知识点?

512 阅读5分钟

先有问题再有答案

  1. transform有哪些应用场景?
  2. 为什么transform性能较好? 
  3. 移动变化的本质原理是什么?
  4. translate移动的基准点哪里?
  5. 视觉位置和dom位置有什么区别?
  6. translate(100%)是相对于父元素还是自身?

transform应用场景

transform 属性在网页设计中非常强大,能够创建多种动画和交互效果。通过结合不同的变换函数(如 translatescalerotate 和 skew),可以实现丰富的视觉效果,并且利用 GPU 加速,确保性能良好。

transform 属性的应用场景可以大致分为两类:视觉变换和动画效果

视觉变换

主要用于静态布局调整和简单的视觉效果
例如实现元素居中:

<div class="container">
    <div class="box"></div>
</div>
.container {
    position: relative;
    width: 200px;
    height: 200px;
    background-color: lightgray;
}

.box {
    position: absolute;
    width: 100px;
    height: 100px;
    background-color: coral;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

动画变换

与 transition 或 animation 一起使用,创建复杂和高效的动画效果,如平滑的移动、旋转、缩放等。

<div class="animated-box"></div>
.animated-box {
    width: 100px;
    height: 100px;
    background-color: skyblue;
    transition: transform 0.5s ease;
}

.animated-box:hover {
    transform: rotate(45deg) scale(1.2);
}

transform的本质:

视觉变换而非布局变换

  • 使用 transform 时,元素的实际位置(在 DOM 中的位置)并没有发生变化,而是通过变换矩阵(Transformation Matrix)来改变元素的视觉呈现。
  • 浏览器使用一个 4x4 的变换矩阵来计算元素的最终位置和形状。translateXtranslateYscalerotate 等操作都会更新这个矩阵,最终由 GPU 渲染出变换后的效果。

变换矩阵的作用

  • 变换矩阵是一个数学工具,用来描述元素的各种变换(平移、缩放、旋转等)。例如:
    • translateX(100px) 会更新矩阵中的平移值,使元素在屏幕上向右移动 100 像素。
    • scale(2) 会更新矩阵中的缩放值,使元素在视觉上放大两倍。
  • 这些变换操作是直接在 GPU 上完成的,因此非常高效。

transform性能好的原因

1. 不触发重排(Reflow)和重绘(Repaint)

  • 当你使用 transform 属性改变元素的位置、大小或旋转时,它 不会影响文档流,也不会导致页面的重新布局(重排)。
  • 传统的属性(如 topleft 等)会改变元素的布局位置,导致浏览器重新计算布局(重排),这是一项性能开销较大的操作。
  • transform 只影响元素的视觉呈现,而不会改变 DOM 树或布局,因此避免了重排和重绘。

2. 硬件加速(GPU 加速)

  • transform 属性可以利用 GPU 进行硬件加速,而不是依赖 CPU 的计算。
  • 当使用 transform 时,浏览器会将相关的元素提升到一个单独的渲染层(composite layer),并在 GPU 上执行渲染操作。这种方式可以显著提高性能,尤其是在动画中。
  • 传统的 topleft 等属性通常由 CPU 处理,性能会相对较差,尤其是在复杂页面或低性能设备上。

3. 合成阶段(Compositing)优化

transform 属性的变换通常在浏览器的合成线程(compositing thread)上起作用,而不是在主线程上。这是 transform 属性在性能优化方面的一个重要优势。

thread.png

详细参考 浏览器:帧&渲染流程

  • 浏览器的渲染过程分为多步,包括:

    1. 布局(Layout):  计算元素的位置和大小。
    2. 绘制(Paint):  绘制像素内容。
    3. 合成(Compositing):  将不同的图层组合在一起,渲染到屏幕上。
  • 当应用 transform 属性时,浏览器可能会将元素提升为一个独立的合成层(compositing layer)。这使得变换操作只需在最后的 合成阶段 进行调整,而无需重新布局或重新绘制。这种优化使得 transform 操作,可以在合成线程处理,而不需要经过主线程的繁重计算。有效的缩短了渲染路径

translate(100%)的基准点与取值

基准点

transform: translate(100%) 是相对于元素 自身的坐标系 来进行移动的, 而不是其视觉位置或父级容器的位置。

使用 transform: translateX(100%) 时,元素的实际 DOM 位置不变,但在视觉上,它会被移动。也就是说,元素在文档流中的占位仍然保持不变,但在页面上看到的效果是它被移动了。

截屏2025-01-09 17.37.44.png

具体来说元素所在位置为ele1,当应用translate(100%)时会将元素向右移动其自身宽度的100%即视觉上来到ele2的位置。

在此基础上 我们在设置translate(-100%) 这时候虽然视觉上在ele2的位置 但是原始点依然在ele1的位置,所以当我们设置值为-100%时 元素会移动到ele3的位置。

百分比取值

100% 的取值是相对于 元素自身的宽度,而不是其父元素的宽度。这意味着无论父元素有多宽,translateX(100%) 总是移动元素自身宽度的 100%。

效果

trans.gif

累计值

所以当我们想让元素以视觉所在位置为基准点移动时 需要对transform的值做累积处理。即先获取当前值 然后再加减对应的具体值。

录屏2025-01-09 20.22.39.gif

<script setup lang="ts">
import { ref } from 'vue';

const boxStyle = ref({});

const moveX100Percent = () => {
    boxStyle.value = {
        transform: 'translateX(100%)',
    };
};

const moveXLeft100Percent = () => {
    boxStyle.value = {
        transform: 'translateX(-100%)',
    };
};
const moveX20Px = () => {
    // 如果 boxStyle.value.transform 已经存在,则在其基础上追加移动距离
    if (boxStyle.value.transform) {
        boxStyle.value.transform += ' translateX(20px)';
    } else {
        boxStyle.value.transform = 'translateX(20px)';
    }
};
</script>

<template>
    <div class="page">
        <div class="parent">
            <div :class="['son']" :style="boxStyle"></div>
        </div>
    </div>
    <div class="btn" @click="moveX100Percent">moveX100%</div>
    <div class="btn" @click="moveX20Px">right10</div>
    <div class="btn" @click="moveXLeft100Percent">moveXLeft100%</div>
</template>

<style lang="scss" scoped>
.parent {
    margin-top: 100px;
    margin-left: 120px;
    width: 100px;
    height: 50px;
    background-color: red;
}

.son {
    width: 100px;
    height: 50px;
    background-color: blue;
    transition: transform 0.5s ease-in-out;
}

.btn {
    margin-top: 20px;
    display: inline-block;
    padding: 15px 20px;
    border-radius: 15px;
    background: green;
}
</style>