给单文件 2048 加新块出现动画:CSS transition 的一处小坑
新块凭空出现,是单文件 2048 里最容易被忽略的体验缺口:每次合并后棋盘上突然多一块 2 或 4,玩家视线跟不上,常常要重新扫一遍棋盘才知道新块落在哪。
最直觉的做法是给格子加 transition:
.tile { transition: transform 120ms ease, opacity 120ms ease; }
.tile-new { transform: scale(0.6); opacity: 0; }
然后在 JS 里先渲染带 .tile-new 的格子,再去掉这个类,期望它从小变大淡入。但第一次写大概率会发现动画根本没播:格子直接以最终大小出现。
坑在这里:transition 只在“样式发生变化”时才触发。如果元素插入 DOM 时就已经是最终样式,浏览器没有“变化”可播。正确做法是分两步:先以初始态插入,再下一帧改到最终态。
function spawnTile(value, row, col) {
const el = document.createElement('div');
el.className = 'tile tile-new';
el.textContent = value;
positionTile(el, row, col); // 先摆到位置上,但仍是 scale(0.6)
board.appendChild(el);
requestAnimationFrame(() => {
el.classList.remove('tile-new'); // 下一帧去掉初始态,transition 开始播
});
}
requestAnimationFrame 是关键:它保证浏览器先把初始态画出来,再处理样式变化,transition 才有起点和终点。
如果不想管帧时序,也可以用 @keyframes,一步到位:
@keyframes pop-in {
from { transform: scale(0.6); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
.tile-new { animation: pop-in 120ms ease; }
animation 插入即播,不需要等下一帧,写起来更省事。代价是动画播完后要记得去掉 .tile-new 这个类,否则下次复用这个格子元素时动画会重播一次。
还有一个细节:新块动画只给“新生成”的格子,合并产生的格子不要复用同一套。合并格可以单独给一个更短的弹跳(比如 scale 1.15 再回落),玩家一眼就能区分“刚长出来的”和“刚合并的”,棋盘变化一下就读懂了。
总结:transition 需要样式变化才触发,插入时先给初始态、下一帧再改终态;嫌麻烦就直接用 @keyframes。动画时长 120ms 左右就够,太长会拖慢连续操作的节奏。
- 点赞
- 收藏
- 关注作者
评论(0)