CSS 排序列表时的转换
CSS transition when sorting a list
我正在尝试在 table 的行上创建一个简单的 css 背景过渡,因为项目从非活动状态变为活动状态。
过渡应该从蓝色(active === false)淡化到粉红色(active === true)。
当从蓝色变为粉红色并且顺序发生变化时,过渡按预期工作,但当从粉色变为蓝色并且顺序发生变化时则没有。
我已经为我的问题做了一个简单的例子https://codesandbox.io/s/simple-react-8pf4s
在此先感谢您的帮助!
这是一个奇怪的行为,但我假设当 React 正在控制将 dom 节点插入其新位置时,class 已经添加,因此会立即呈现。为了抵消这一点,我创建了一个关键帧动画和一堆正在动画以将此动画应用到的 id。我可以通过在创建引用时记录引用来确认相同的元素正在被重用(而不是被破坏和重新创建)。
https://codesandbox.io/s/objective-dijkstra-ifren
const App = () => {
const [animating, setAnimating] = useState([]);
const removeAnimation = id => {
setAnimating(animating.filter(x => x !== id));
};
const [data, setData] = useState(sourceData);
const toggle = id => {
let data2 = [...data];
const index = data.findIndex(x => x.id === id);
data2[index] = { ...data2[index], active: !data2[index].active };
setAnimating([...animating, data2[index].id]);
data2 = [...data2.filter(x => x.active), ...data2.filter(x => !x.active)];
setData(data2);
};
return (
<div className="App">
{data.map(d => (
<div
key={d.id}
className={cl(
"row",
d.active && "row_active",
!d.active && animating.includes(d.id) && "row_hasAnimation",
d.active && animating.includes(d.id) && "row_active_hasAnimation"
)}
onAnimationEnd={() => removeAnimation(d.id)}
>
<div className="cell">{d[1]}</div>
<div className="cell">{d[2]}</div>
<div className="cell">{d[3]}</div>
<div className="cell">
<button onClick={() => toggle(d.id)}>Toggle</button>
</div>
</div>
))}
</div>
);
};
这可能就是 React 处理列表协调的方式。
当顶部项目变为非活动状态时,它首先从 DOM 中删除,然后作为新的子项附加。这就是为什么没有过渡。当您将底部项目变为活动状态时,过渡会起作用,因为它再次是要删除的顶部项目,因此过渡项目会保留在 DOM.
中
在这种特定情况下,在 table 行上设置键没有帮助,因为据我所知 this,它们仅用于优化,实际上并不能保证将重复使用相同的 DOM 个元素。
您可能会遇到提到的相同问题 here。
您可以查看一些用于转换列表的库以找到潜在的解决方案。例如。 react-flip-move
我的一部分希望我是错的,因为它有点糟透了,让一件简单的事情变得非常复杂。
我能够为您解决排序问题,但真正难倒的是 CSS 问题..
问题与 keys
和创建项目映射有关。据我所知,React 会将转换应用到特定键。切换到非活动状态后,React 就像将其视为一个全新的节点,并且由于某种原因它没有正确应用转换..
我不确定为什么这会以一种方式起作用而不是另一种方式,这对我来说真的很奇怪......我无法在网上找到解决这个问题的方法,我测试了一堆东西......你应该看看使用 React 和键进入 CSS 转换..
话虽如此,我确实想出了一些 "hacky" 方法来完成此任务..
CSS 作为 JS 对象:
const { useState, useEffect } = React;
const { render } = ReactDOM;
const styles = {
fontFamily: "sans-serif",
textAlign: "center"
};
const sourceData = [
{
id: "es",
1: "uno",
2: "dos",
3: "tres",
active: true
},
{
id: "de",
1: "eine",
2: "zwei",
3: "drei",
active: false
},
{
id: "abc",
1: "a",
2: "b",
3: "c",
active: false
}
];
const activeClass = {
background: "pink",
color: "blue",
transition: "all 1s"
};
const inactiveClass = {
background: "blue",
color: "pink",
transition: "all 1s"
};
const App = () => {
const [data, setData] = useState(sourceData);
useEffect(() => {
setClassNames();
}, [data]);
const sortData = d => d.sort((a, b) => (a[1] < b[1] ? -1 : 1));
const sortAllData = d => [
...sortData(d.filter(i => i.active)),
...sortData(d.filter(i => !i.active))
];
const handleToggle = index => event => {
let clone = [...data];
clone[index].active = !clone[index].active;
setData(sortAllData(clone));
};
const setClassNames = () => {
let actives = document.querySelectorAll(`[dataactive=${true}]`);
let inactives = document.querySelectorAll(`[dataactive=${false}]`);
setTimeout(() => {
actives.forEach(a => {
Object.keys(activeClass).forEach(k => a.style[k] = activeClass[k])
});
inactives.forEach(ina => {
Object.keys(inactiveClass).forEach(k => ina.style[k] = inactiveClass[k])
});
}, 10);
};
return (
<div style={styles}>
<table>
<thead>
<tr>
<td>Active?</td>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</thead>
<tbody>
{data.map((d, index) => {
return (
<tr dataactive={d.active.toString()} key={d.id} id={d.id}>
<td>{d.active.toString()}</td>
<td>{d[1]}</td>
<td>{d[2]}</td>
<td>{d[3]}</td>
<td>
<button onClick={handleToggle(index)}>TOGGLE</button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
};
render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>
使用 .CSS 文件:
const { useState, useEffect } = React;
const { render } = ReactDOM;
const styles = {
fontFamily: "sans-serif",
textAlign: "center"
};
const sourceData = [
{
id: "es",
1: "uno",
2: "dos",
3: "tres",
active: true
},
{
id: "de",
1: "eine",
2: "zwei",
3: "drei",
active: false
},
{
id: "abc",
1: "a",
2: "b",
3: "c",
active: false
}
];
const App = () => {
const [data, setData] = useState(sourceData);
useEffect(() => {
setClassNames();
}, [data]);
const sortData = d => d.sort((a, b) => (a[1] < b[1] ? -1 : 1));
const sortAllData = d => [
...sortData(d.filter(i => i.active)),
...sortData(d.filter(i => !i.active))
];
const handleToggle = index => event => {
let clone = [...data];
clone[index].active = !clone[index].active;
setData(sortAllData(clone));
};
const setClassNames = () => {
let actives = document.querySelectorAll(`[dataactive=${true}]`);
let inactives = document.querySelectorAll(`[dataactive=${false}]`);
setTimeout(() => {
actives.forEach(a => a.className = "active");
inactives.forEach(ina => ina.className = "inactive");
}, 10);
};
return (
<div style={styles}>
<table>
<thead>
<tr>
<td>Active?</td>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</thead>
<tbody>
{data.map((d, index) => {
return (
<tr dataactive={d.active.toString()} key={d.id} id={d.id}>
<td>{d.active.toString()}</td>
<td>{d[1]}</td>
<td>{d[2]}</td>
<td>{d[3]}</td>
<td>
<button onClick={handleToggle(index)}>TOGGLE</button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
};
render(<App />, document.body);
.active {
background: pink;
color: blue;
transition: all 1s;
}
.inactive {
background: blue;
color: pink;
transition: all 1s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>
我正在尝试在 table 的行上创建一个简单的 css 背景过渡,因为项目从非活动状态变为活动状态。
过渡应该从蓝色(active === false)淡化到粉红色(active === true)。
当从蓝色变为粉红色并且顺序发生变化时,过渡按预期工作,但当从粉色变为蓝色并且顺序发生变化时则没有。
我已经为我的问题做了一个简单的例子https://codesandbox.io/s/simple-react-8pf4s
在此先感谢您的帮助!
这是一个奇怪的行为,但我假设当 React 正在控制将 dom 节点插入其新位置时,class 已经添加,因此会立即呈现。为了抵消这一点,我创建了一个关键帧动画和一堆正在动画以将此动画应用到的 id。我可以通过在创建引用时记录引用来确认相同的元素正在被重用(而不是被破坏和重新创建)。
https://codesandbox.io/s/objective-dijkstra-ifren
const App = () => {
const [animating, setAnimating] = useState([]);
const removeAnimation = id => {
setAnimating(animating.filter(x => x !== id));
};
const [data, setData] = useState(sourceData);
const toggle = id => {
let data2 = [...data];
const index = data.findIndex(x => x.id === id);
data2[index] = { ...data2[index], active: !data2[index].active };
setAnimating([...animating, data2[index].id]);
data2 = [...data2.filter(x => x.active), ...data2.filter(x => !x.active)];
setData(data2);
};
return (
<div className="App">
{data.map(d => (
<div
key={d.id}
className={cl(
"row",
d.active && "row_active",
!d.active && animating.includes(d.id) && "row_hasAnimation",
d.active && animating.includes(d.id) && "row_active_hasAnimation"
)}
onAnimationEnd={() => removeAnimation(d.id)}
>
<div className="cell">{d[1]}</div>
<div className="cell">{d[2]}</div>
<div className="cell">{d[3]}</div>
<div className="cell">
<button onClick={() => toggle(d.id)}>Toggle</button>
</div>
</div>
))}
</div>
);
};
这可能就是 React 处理列表协调的方式。
当顶部项目变为非活动状态时,它首先从 DOM 中删除,然后作为新的子项附加。这就是为什么没有过渡。当您将底部项目变为活动状态时,过渡会起作用,因为它再次是要删除的顶部项目,因此过渡项目会保留在 DOM.
中在这种特定情况下,在 table 行上设置键没有帮助,因为据我所知 this,它们仅用于优化,实际上并不能保证将重复使用相同的 DOM 个元素。
您可能会遇到提到的相同问题 here。
您可以查看一些用于转换列表的库以找到潜在的解决方案。例如。 react-flip-move
我的一部分希望我是错的,因为它有点糟透了,让一件简单的事情变得非常复杂。
我能够为您解决排序问题,但真正难倒的是 CSS 问题..
问题与 keys
和创建项目映射有关。据我所知,React 会将转换应用到特定键。切换到非活动状态后,React 就像将其视为一个全新的节点,并且由于某种原因它没有正确应用转换..
我不确定为什么这会以一种方式起作用而不是另一种方式,这对我来说真的很奇怪......我无法在网上找到解决这个问题的方法,我测试了一堆东西......你应该看看使用 React 和键进入 CSS 转换..
话虽如此,我确实想出了一些 "hacky" 方法来完成此任务..
CSS 作为 JS 对象:
const { useState, useEffect } = React;
const { render } = ReactDOM;
const styles = {
fontFamily: "sans-serif",
textAlign: "center"
};
const sourceData = [
{
id: "es",
1: "uno",
2: "dos",
3: "tres",
active: true
},
{
id: "de",
1: "eine",
2: "zwei",
3: "drei",
active: false
},
{
id: "abc",
1: "a",
2: "b",
3: "c",
active: false
}
];
const activeClass = {
background: "pink",
color: "blue",
transition: "all 1s"
};
const inactiveClass = {
background: "blue",
color: "pink",
transition: "all 1s"
};
const App = () => {
const [data, setData] = useState(sourceData);
useEffect(() => {
setClassNames();
}, [data]);
const sortData = d => d.sort((a, b) => (a[1] < b[1] ? -1 : 1));
const sortAllData = d => [
...sortData(d.filter(i => i.active)),
...sortData(d.filter(i => !i.active))
];
const handleToggle = index => event => {
let clone = [...data];
clone[index].active = !clone[index].active;
setData(sortAllData(clone));
};
const setClassNames = () => {
let actives = document.querySelectorAll(`[dataactive=${true}]`);
let inactives = document.querySelectorAll(`[dataactive=${false}]`);
setTimeout(() => {
actives.forEach(a => {
Object.keys(activeClass).forEach(k => a.style[k] = activeClass[k])
});
inactives.forEach(ina => {
Object.keys(inactiveClass).forEach(k => ina.style[k] = inactiveClass[k])
});
}, 10);
};
return (
<div style={styles}>
<table>
<thead>
<tr>
<td>Active?</td>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</thead>
<tbody>
{data.map((d, index) => {
return (
<tr dataactive={d.active.toString()} key={d.id} id={d.id}>
<td>{d.active.toString()}</td>
<td>{d[1]}</td>
<td>{d[2]}</td>
<td>{d[3]}</td>
<td>
<button onClick={handleToggle(index)}>TOGGLE</button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
};
render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>
使用 .CSS 文件:
const { useState, useEffect } = React;
const { render } = ReactDOM;
const styles = {
fontFamily: "sans-serif",
textAlign: "center"
};
const sourceData = [
{
id: "es",
1: "uno",
2: "dos",
3: "tres",
active: true
},
{
id: "de",
1: "eine",
2: "zwei",
3: "drei",
active: false
},
{
id: "abc",
1: "a",
2: "b",
3: "c",
active: false
}
];
const App = () => {
const [data, setData] = useState(sourceData);
useEffect(() => {
setClassNames();
}, [data]);
const sortData = d => d.sort((a, b) => (a[1] < b[1] ? -1 : 1));
const sortAllData = d => [
...sortData(d.filter(i => i.active)),
...sortData(d.filter(i => !i.active))
];
const handleToggle = index => event => {
let clone = [...data];
clone[index].active = !clone[index].active;
setData(sortAllData(clone));
};
const setClassNames = () => {
let actives = document.querySelectorAll(`[dataactive=${true}]`);
let inactives = document.querySelectorAll(`[dataactive=${false}]`);
setTimeout(() => {
actives.forEach(a => a.className = "active");
inactives.forEach(ina => ina.className = "inactive");
}, 10);
};
return (
<div style={styles}>
<table>
<thead>
<tr>
<td>Active?</td>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</thead>
<tbody>
{data.map((d, index) => {
return (
<tr dataactive={d.active.toString()} key={d.id} id={d.id}>
<td>{d.active.toString()}</td>
<td>{d[1]}</td>
<td>{d[2]}</td>
<td>{d[3]}</td>
<td>
<button onClick={handleToggle(index)}>TOGGLE</button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
};
render(<App />, document.body);
.active {
background: pink;
color: blue;
transition: all 1s;
}
.inactive {
background: blue;
color: pink;
transition: all 1s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>