1. 业务场景
在开发图片编辑器、拖拽布局或看板工具时,我们经常需要让元素支持 90 度倍数的旋转。
- 需求:用户点击旋转按钮,图片原地转 90 度。
- 挑战:旋转后,我们需要知道图片现在实际占据了多大的空间(即真实的
offsetWidth和offsetHeight),以便进行碰撞检测、边界限制或自动排版。
2. 技术痛点
如果我们只给图片加一个 CSS transform: rotate(90deg):
- 视觉上:图片确实竖起来了。
- 物理上:浏览器认为它还是横着的。你通过 JS 拿到的
offsetWidth依然是旋转前的宽度。 - 后果:你的碰撞检测会失效,因为浏览器不知道它现在已经“变瘦变高”了。
3. 技术架构:老板与员工模型
为了解决这个问题,我们采用双层嵌套结构,并赋予它们不同的职责:
角色定义
- 老板(内层图片) :负责视觉表现。它的初始宽高是从员工那里“继承”来的,它只管拿着这个尺寸去旋转。
- 员工(外层容器) :负责物理占位。它是一个“懂事”的盒子,会根据老板旋转后的样子,动态修改自己的宽高,从而给浏览器提供一个真实的物理外壳。
核心逻辑流
- 初始状态:员工是横向(200x100),老板作为子元素也拿到这个尺寸,两者重合。
- 老板先动:老板执行
rotate(90deg)。此时老板视觉上变成了竖向(100x200),但它身上的 DOM 属性还没变。 - 员工配合:员工发现老板转成了竖向,于是把自己的
width改成 100,height改成 200。 - 结果:员工撑开了一个真实的竖向空间,老板正好完美填充其中。
4. 场景全解析
| 场景 | 动作描述 | 老板(图片)状态 | 员工(容器)动作 | 最终物理占位 |
|---|---|---|---|---|
| 初始状态 | 0° (横向) | 宽 200, 高 100 | 保持宽 200, 高 100 | 横向 (200x100) |
| 右转 90° | 0° → 90° | 旋转 90° (视觉变竖向) | 交换宽高 (变 100x200) | 竖向 (100x200) |
| 再转 90° | 90° → 180° | 旋转 180° (视觉回横向) | 再次交换 (变回 200x100) | 横向 (200x100) |
| 连续旋转 | 任意 90° 倍数 | 持续累加角度 | 只要横竖状态改变就交换宽高 | 实时同步 |
5. 关键实现细节
5.1 内部视觉居中
为确保老板(图片)始终在员工(容器)内视觉居中,我们需要为老板设置如下 CSS:
.img-wrap {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) rotate(...);
transform-origin: center center;
}
这样,无论员工容器的尺寸如何变化,图片的几何中心都会与容器的中心点重合。
5.2 外部定位与“原地旋转”
当员工容器的宽高因状态切换而改变时,如果其 left, top坐标不变,元素的左上角位置会发生变化,导致视觉上的“跳动”。
为了实现“原地旋转”(即围绕自身中心点旋转),我们需要在容器尺寸变化时,动态调整其定位坐标,使其中心点保持不变。
计算逻辑如下:
-
记录旧中心点:在改变角度前,计算当前容器(员工)的中心点坐标。
centerX = left + offsetWidth / 2centerY = top + offsetHeight / 2
-
计算新尺寸:根据新角度判断横竖状态,得出容器的新宽高。
-
反推新位置:根据“中心点不变”的原则,计算出容器新的左上角坐标。
newLeft = centerX - newWidth / 2newTop = centerY - newHeight / 2
6. 完整代码实现
7. 总结
-
核心价值:通过“员工”容器动态调整自身尺寸,我们成功获取了与视觉旋转状态一致的真实物理占位尺寸,解决了碰撞检测等功能的计算基础问题。
-
架构清晰:“老板-员工”模型实现了完美的职责分离。老板(内层)只关心视觉表现(旋转),员工(外层)只关心物理布局(占位)。
-
实现关键:
- 横竖状态判断:利用
角度 % 180的结果判断是 0°(横)还是 90°(竖)。 - 中心点对齐:通过 CSS 使内层元素在容器中绝对居中。
- 位置修正:在容器尺寸变化时,通过中心点坐标反推新位置,实现“原地旋转”效果。
- 横竖状态判断:利用
-
适用场景:本方案完美解决了图片、元素在 90 度倍数旋转时,物理尺寸与视觉状态不同步的通用前端布局难题。