1. 核心目标
业务场景
在一个可拖拽、可旋转的图片编辑器中,用户点击"旋转 90°"按钮后:
- 图片需要原地旋转(中心点不动)
- 旋转后要能拿到图片实际占据的位置和尺寸(
left、top、width、height) - 这些数据用于后续的碰撞检测、边界限制、保存布局等
技术痛点
如果直接用 transform: rotate(90deg) 旋转图片:
- 视觉上:图片确实转了 90° ✅
- 但 DOM 尺寸:
offsetWidth和offsetHeight还是旋转前的值 ❌ - 原因:
transform只改变视觉效果,不改变元素的真实布局尺寸
解决方案
通过内外两层结构实现:
- 外层容器:通过交换
width/height改变真实尺寸,让浏览器更新 DOM 尺寸 - 内层包装器:通过
transform: rotate()实现视觉旋转 - 手动纠偏:调整包装器的宽高属性,确保旋转后完美填充容器
最终效果:既能拿到真实的 offsetWidth/Height,又能看到平滑的旋转动画 ✅
2. 解决方案:三步走策略
第一步:外部容器(负责"物理变形")
- 动作:直接交换容器的
width和height。 - 目的:让浏览器认为这个盒子真的从"横向"变成了"竖向",从而更新
offsetWidth/offsetHeight。
第二步:内部包装器(被动跟随容器)
- 机制:通过
width: 100%; height: 100%让包装器自动匹配容器的尺寸。 - 结果:容器变竖向后,包装器也被动挤成竖向。
第三步:包装器自转 + 手动纠偏(核心逻辑)
问题的本质:旋转导致方向反转
我们用 "老板与员工" 来理解整个流程:
场景 1:旋转 90°
-
老板(容器)先变形:
- 老板把自己从
200×100(横向)改成100×200(竖向)
- 老板把自己从
-
员工(包装器)被动跟随:
- 员工因为宽高 100%,被老板挤成
100×200(竖向)✅
- 员工因为宽高 100%,被老板挤成
-
员工自己旋转:
- 员工执行
rotate(90deg) - 问题:竖向的
100×200转 90° 后,变成横向的视觉效果 ❌ - 和老板要求的竖向完全相反!
- 员工执行
-
解决方案:先捏成横向,再旋转
- 既然直接旋转会反向,那我们就先把员工捏成横向(
200×100) - 然后对这个横向框执行
rotate(90deg)→ 转完正好变成竖向 ✅
if (isVertical) { // 员工原本是竖向的,但直接旋转会变成横向 // 所以要先把员工"捏成"横向,再旋转才能变成竖向 imgWrap.style.width = state.h + 'px'; // 200px imgWrap.style.height = state.w + 'px'; // 100px } - 既然直接旋转会反向,那我们就先把员工捏成横向(
场景 2:旋转 180°
-
老板(容器) 转了一圈又变回
200×100(横向) -
员工(包装器) 被动跟随,也是
200×100(横向) -
员工自己旋转:
- 员工执行
rotate(180deg) - 横向转 180° → 还是横向 ✅
- 天生匹配,无需调整!
- 员工执行
3. 完整实现代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>获取旋转后真实宽高 Demo</title>
<style>
body { padding: 50px; background: #f0f0f0; }
.stage { position: relative; width: 500px; height: 500px; background: #ddd; border: 2px solid #999; }
/* 外部容器:通过改变真实尺寸来获取 offsetWidth/Height */
.box {
position: absolute;
transition: all 0.3s ease;
overflow: hidden; /* 裁剪掉包装器溢出部分 */
background: white; border: 2px solid blue;
}
/* 内部包装器:负责居中和内容旋转 */
.img-wrap {
position: absolute; left: 50%; top: 50%;
transform-origin: center center;
transition: all 0.3s ease;
}
.img-wrap img { width: 100%; height: 100%; object-fit: cover; }
button { padding: 8px 16px; margin-right: 10px; cursor: pointer; }
.log { font-family: monospace; background: #fff; padding: 5px; display: inline-block; }
</style>
</head>
<body>
<div style="margin-bottom: 20px;">
<button onclick="rotate(90)">右转 90°</button>
<button onclick="rotate(-90)">左转 90°</button>
<button onclick="reset()">重置</button>
<div class="log" id="log"></div>
</div>
<div class="stage">
<div class="box" id="box">
<div class="img-wrap" id="imgWrap">
<img src="https://picsum.photos/200/100" alt="示例">
</div>
</div>
</div>
<script>
// 状态记录:存储盒子的位置、尺寸和旋转角度
let state = { left: 100, top: 100, w: 200, h: 100, deg: 0 };
const box = document.getElementById('box');
const imgWrap = document.getElementById('imgWrap');
const log = document.getElementById('log');
function render() {
// 1. 更新外部容器的物理位置和尺寸(这就是我们想要的真实占位)
box.style.left = state.left + 'px';
box.style.top = state.top + 'px';
box.style.width = state.w + 'px';
box.style.height = state.h + 'px';
// 2. 【关键】让员工(包装器)的宽高属性匹配旋转后的视觉效果
// 流程拆解:
// ① 老板(容器)先从 200×100 变成 100×200(竖向)
// ② 员工(包装器)因宽高 100%,被动挤成 100×200(竖向)
// ③ 员工执行 rotate(90deg),但竖向框转 90° 会变成横向 → 方向反了!
// ④ 解决方案:先把员工"捏成"横向(200×100),再旋转就正好变成竖向
const isVertical = (Math.abs(state.deg) % 180 === 90);
if (isVertical) {
// 老板现在是竖向的,员工要先捏成横向,再旋转才能变成竖向
imgWrap.style.width = state.h + 'px';
imgWrap.style.height = state.w + 'px';
} else {
// 老板还是横向的,员工保持原样,旋转后方向正确
imgWrap.style.width = state.w + 'px';
imgWrap.style.height = state.h + 'px';
}
// 3. 执行视觉旋转
imgWrap.style.transform = `translate(-50%, -50%) rotate(${state.deg}deg)`;
// 打印真实占位信息(可用于碰撞检测、边界限制等)
log.innerHTML = `真实宽: ${box.offsetWidth}px | 真实高: ${box.offsetHeight}px | 角度: ${state.deg}°`;
}
function rotate(angle) {
// 记录旋转前的中心点(确保原地旋转)
const cx = state.left + state.w / 2;
const cy = state.top + state.h / 2;
// 交换宽高(模拟物理旋转)
[state.w, state.h] = [state.h, state.w];
// 重新计算左上角,确保中心点不动
state.left = cx - state.w / 2;
state.top = cy - state.h / 2;
state.deg += angle;
render();
}
function reset() {
state = { left: 100, top: 100, w: 200, h: 100, deg: 0 };
render();
}
render();
</script>
</body>
</html>
4. 总结
核心价值
- 解决痛点:
transform旋转无法获取真实 DOM 尺寸 - 实现效果:既能拿到准确的
offsetWidth/Height,又有平滑的旋转动画 - 应用场景:图片编辑器、拖拽布局、碰撞检测、边界限制
核心思想:三步走 + 预补偿
-
老板变形:容器交换宽高,改变物理尺寸
-
员工跟随:包装器通过宽高 100% 被动匹配容器
-
员工自转并调整:
- 如果直接旋转会反向 → 先捏成相反的形状,再旋转就对了
- 如果旋转后方向正确 → 无需调整
记忆口诀
- 老板变竖了 → 员工被挤成竖向 → 但竖向转 90° 会变横向 → 所以要先捏成横向,再转才变竖向
- 老板还是横的 → 员工也是横向 → 横向转 180° 还是横向 → 不用调整
本质原理
rotate(90deg) 会让元素的方向反转。为了让旋转后的结果符合预期,我们需要在旋转前预补偿——把元素捏成相反的形状,这样转完就正好对了!