css 转换:翻译转换行为异常
css transform: translate transition behaving strangely
在 this sandbox,我重新制作了经典的滑动益智游戏。
在我的 GameBlock
组件上,我使用了 css transform: translate(x,y)
和 transition: transform
的组合来为滑动的游戏组件设置动画:
const StyledGameBlock = styled.div<{
index: number;
isNextToSpace: boolean;
backgroundColor: string;
}>`
position: absolute;
display: flex;
justify-content: center;
align-items: center;
width: ${BLOCK_SIZE}px;
height: ${BLOCK_SIZE}px;
background-color: ${({ backgroundColor }) => backgroundColor};
${({ isNextToSpace }) => isNextToSpace && "cursor: pointer"};
${({ index }) => css`
transform: translate(
${getX(index) * BLOCK_SIZE}px,
${getY(index) * BLOCK_SIZE}px
);
`}
transition: transform 400ms;
`;
基本上,我在板上使用块的当前索引来计算它的 x
和 y
值,这些值会在块被打开时更改块的 transform: translate
值感动了。
虽然这确实能够在将方块滑动到顶部、向右和向左时触发平滑过渡 - 但出于某种原因,从上到下滑动方块不会平滑过渡。
知道是什么导致了这个异常吗?
React、列表和键
您看到的是 <GameBlock />
个组件的 mount/unmount 的结果。
尽管您将 key
道具传递给组件,但 React 不确定您是否仍在渲染相同的元素。
如果我必须猜测为什么 React 不确定,我会把罪魁祸首放在:
- 更改数组排序方式:
const previousSpace = gameBlocks[spaceIndex];
gameBlocks[spaceIndex] = gameBlocks[index];
gameBlocks[index] = previousSpace;
- 使用条件
isSpace
具有不同的虚拟 DOM 结果:
({ correctIndex, currentIndex, isSpace, isNextToSpace }) => isSpace ? null : ( <GameBlock ....
通常在应用程序中,我们不介意 re-mount 因为它非常快。当我们附加动画时,我们不想要任何 re-mount,因为它们会与 css-transitions.
混淆
为了让 React 确定它是同一个节点,不需要 re-mount。我们应该注意这一点;渲染之间;虚拟 dom 基本保持不变。
我们可以实现在列表的渲染中不做任何花哨的事情,并在渲染之间传递相同的键。
向下传递 isSpace
而不是改变呈现的 DOM 节点,我们希望列表呈现总是 return 等量的节点,具有完全 相同的键 对于每个节点,相同的顺序。
只需将 'isSpace' 向下传递并设置为 display:none;
的样式即可。
<GameBlock
...
isSpace={isSpace}
...
>
const StyledGameBlock = styled.div<{ ....}>`
...
display: ${({isSpace})=> isSpace? 'none':'flex'};
...
`;
确保不更改数组排序
React 认为要修改 gameBlocks
数组,键的顺序不同。因此触发渲染的 <GameBlock/>
组件的 unmount/mount。
我们可以确保 React 认为这个数组是未修改的,只需更改列表中项目的属性而不是排序本身。
在您的情况下,我们可以保留所有属性,只更改彼此 moved/swapped 的块的 currentIndex
。
const onMove = useCallback(
(index) => {
const newSpaceIndex = gameBlocks[index].currentIndex; // the space will get the current index of the clicked block.
const movedBlockNewIndex = gameBlocks[spaceIndex].currentIndex; // the clicked block will get the index of the space.
setState({
spaceIndex: spaceIndex, // the space will always have the same index in the array.
gameBlocks: gameBlocks.map((block) => {
const isMovingBlock = index === block.correctIndex; // check if this block is the one that was clicked
const isSpaceBlock =
gameBlocks[spaceIndex].currentIndex === block.currentIndex; // check if this block is the space block.
let newCurrentIndex = block.currentIndex; // most blocks will stay in their spot.
if (isMovingBlock) {
newCurrentIndex = movedBlockNewIndex; // the moving block will swap with the space.
}
if (isSpaceBlock) {
newCurrentIndex = newSpaceIndex; // the space will swap with the moving block
}
return {
...block,
currentIndex: newCurrentIndex,
isNextToSpace: getIsNextToSpace(newCurrentIndex, newSpaceIndex)
};
})
});
},
[gameBlocks, spaceIndex]
);
...
// we have to be sure to call onMove the with the index of the clicked block.
() => onMove(correctIndex)
我们唯一改变的是点击块的 currentIndex
和 space。
沙盒:
sandbox example 基于您提供的沙箱。
结束语:我认为您的代码易于阅读和理解,做得很好!
除了@Lars 提供的出色回答和解释之外,我还想分享视觉证据,证明某些 <GameBlock />
组件确实按顺序卸载或更改,导致 CSS 动画出现问题。
如您所见,当聚焦其中一个块并向下滑动时,元素会更改其在 DOM 中的位置。
在 this sandbox,我重新制作了经典的滑动益智游戏。
在我的 GameBlock
组件上,我使用了 css transform: translate(x,y)
和 transition: transform
的组合来为滑动的游戏组件设置动画:
const StyledGameBlock = styled.div<{
index: number;
isNextToSpace: boolean;
backgroundColor: string;
}>`
position: absolute;
display: flex;
justify-content: center;
align-items: center;
width: ${BLOCK_SIZE}px;
height: ${BLOCK_SIZE}px;
background-color: ${({ backgroundColor }) => backgroundColor};
${({ isNextToSpace }) => isNextToSpace && "cursor: pointer"};
${({ index }) => css`
transform: translate(
${getX(index) * BLOCK_SIZE}px,
${getY(index) * BLOCK_SIZE}px
);
`}
transition: transform 400ms;
`;
基本上,我在板上使用块的当前索引来计算它的 x
和 y
值,这些值会在块被打开时更改块的 transform: translate
值感动了。
虽然这确实能够在将方块滑动到顶部、向右和向左时触发平滑过渡 - 但出于某种原因,从上到下滑动方块不会平滑过渡。
知道是什么导致了这个异常吗?
React、列表和键
您看到的是 <GameBlock />
个组件的 mount/unmount 的结果。
尽管您将 key
道具传递给组件,但 React 不确定您是否仍在渲染相同的元素。
如果我必须猜测为什么 React 不确定,我会把罪魁祸首放在:
- 更改数组排序方式:
const previousSpace = gameBlocks[spaceIndex];
gameBlocks[spaceIndex] = gameBlocks[index];
gameBlocks[index] = previousSpace;
- 使用条件
isSpace
具有不同的虚拟 DOM 结果:
({ correctIndex, currentIndex, isSpace, isNextToSpace }) => isSpace ? null : ( <GameBlock ....
通常在应用程序中,我们不介意 re-mount 因为它非常快。当我们附加动画时,我们不想要任何 re-mount,因为它们会与 css-transitions.
混淆
为了让 React 确定它是同一个节点,不需要 re-mount。我们应该注意这一点;渲染之间;虚拟 dom 基本保持不变。
我们可以实现在列表的渲染中不做任何花哨的事情,并在渲染之间传递相同的键。
向下传递 isSpace
而不是改变呈现的 DOM 节点,我们希望列表呈现总是 return 等量的节点,具有完全 相同的键 对于每个节点,相同的顺序。
只需将 'isSpace' 向下传递并设置为 display:none;
的样式即可。
<GameBlock
...
isSpace={isSpace}
...
>
const StyledGameBlock = styled.div<{ ....}>`
...
display: ${({isSpace})=> isSpace? 'none':'flex'};
...
`;
确保不更改数组排序
React 认为要修改 gameBlocks
数组,键的顺序不同。因此触发渲染的 <GameBlock/>
组件的 unmount/mount。
我们可以确保 React 认为这个数组是未修改的,只需更改列表中项目的属性而不是排序本身。
在您的情况下,我们可以保留所有属性,只更改彼此 moved/swapped 的块的 currentIndex
。
const onMove = useCallback(
(index) => {
const newSpaceIndex = gameBlocks[index].currentIndex; // the space will get the current index of the clicked block.
const movedBlockNewIndex = gameBlocks[spaceIndex].currentIndex; // the clicked block will get the index of the space.
setState({
spaceIndex: spaceIndex, // the space will always have the same index in the array.
gameBlocks: gameBlocks.map((block) => {
const isMovingBlock = index === block.correctIndex; // check if this block is the one that was clicked
const isSpaceBlock =
gameBlocks[spaceIndex].currentIndex === block.currentIndex; // check if this block is the space block.
let newCurrentIndex = block.currentIndex; // most blocks will stay in their spot.
if (isMovingBlock) {
newCurrentIndex = movedBlockNewIndex; // the moving block will swap with the space.
}
if (isSpaceBlock) {
newCurrentIndex = newSpaceIndex; // the space will swap with the moving block
}
return {
...block,
currentIndex: newCurrentIndex,
isNextToSpace: getIsNextToSpace(newCurrentIndex, newSpaceIndex)
};
})
});
},
[gameBlocks, spaceIndex]
);
...
// we have to be sure to call onMove the with the index of the clicked block.
() => onMove(correctIndex)
我们唯一改变的是点击块的 currentIndex
和 space。
沙盒:
sandbox example 基于您提供的沙箱。
结束语:我认为您的代码易于阅读和理解,做得很好!
除了@Lars 提供的出色回答和解释之外,我还想分享视觉证据,证明某些 <GameBlock />
组件确实按顺序卸载或更改,导致 CSS 动画出现问题。
如您所见,当聚焦其中一个块并向下滑动时,元素会更改其在 DOM 中的位置。