javascript - 使用 React,如何实现具有 css 转换切换(从 fullHeight 到 0px)的基本 Accordion(允许高度保持动态/自动)
问题描述
为了仅使用 HTML 将高度从 auto 转换为 0,CSS 有自己的试验(css 转换不适用于 auto 值)。我使用 Javascript 解决了这个问题,通过...
步骤 1. 为内容高度设置 CSS 转换
步骤 2. 查找内容的实际高度(使用 javascript content.style.offsetHeight
)
步骤 3. 然后将 设置content.style.height
为绝对值(内容的 fullHeight,而不是 auto)。
第 4 步。确保浏览器通过使用强制 DOM 重排content.clientWidth
(例如强制页面重排)按页面注册此更改)
第 5 步。 然后立即将高度设置为 0px。content.style.height = 0
(这将转换开始转换)
问题
但是,现在我正在尝试在 onlick 处理程序中在 React 中编写相同的序列...
第 1 步(我可以) - 仍然使用在高度上设置的 css 过渡
第 2 步(我可以) - 使用 {refs} 获取content.offsetHeight
.
第 3 步(我可以“设置”高度 -但它不会同步更新) - 使用 setState 将异步排队要更新/渲染的元素高度,但不会在第 5 步中的关键行之前。
第 4 步 - 在下一个第 5 步之前,元素需要已经将其高度值更改为绝对初始值(在第 3 步中设置)。因此,第 4 步表示在下一步之前将刷新此更改的内容。
第 5 步 - 然后将高度更改为绝对最终值(这将触发转换 - IF 且仅当第 3 步实际生效时)(记住css 转换只会在元素值从绝对值更改为另一个时发生绝对(不适用于自动值)。
我使用 React 编写了类似的代码(见下文),但由于明显的原因它不起作用(如果您了解代码(没有 React)为什么起作用......它需要立即(同步)将高度更新为绝对值(并且还显示属性)在同一事件处理程序中的几个关键时刻,以便转换工作)。
因为改变高度的必要尝试被困在 Reacts异步抽象后面,所以不得不依靠 setState 来做需要在下一行代码之前立即(同步)完成的事情意味着我无法触发转换(因为我的非-React 版本可以)。
工作代码(不使用 React)链接:
JSfiddle https://jsfiddle.net/mattlusty/rfdvbz16/6/
GitHub https://github.com/mattlusty/toggle-with-react.git
document.querySelector("button").addEventListener("click", () => {
let content = document.querySelector(".content");
let wasOpen = content.classList.contains("open");
content.classList.toggle("open");
// NB: STEP 1 was in the css (transition: height 0.3s)
// STEP 2.1
// ensures it is visible
content.classList.add("block");
// STEP 2.2
// now can get the actual full auto height
let fullHeight = content.offsetHeight;
if (wasOpen) {
// STEP 3
// set initial height explicitly
content.style.height = fullHeight + "px";
// STEP 4
// force reflow
content.offsetWidth;
// STEP 5
// set final height explicitly - which it will transition to
content.style.height = "0px";
// STEP 6
// after transition transitionEnd event handler resets height to auto
// .. (and removes the forced display:block)
} else {
// (Similar steps as other part of IF block) ...
// set initial height explicitly
content.style.height = "0px";
// force reflow
content.offsetWidth;
// set final height explicitly - which it will transition to
content.style.height = fullHeight + "px";
}
});
document
.querySelector(".content")
.addEventListener("transitionend", (event) => {
event.target.classList.remove("block");
event.target.style.height = null;
});
尝试/不工作的代码(与 React 一起使用)链接:
JSFiddle https://jsfiddle.net/mattlusty/L21m0uyg/29
GitHub https://github.com/mattlusty/toggle-with-react.git
import "./app.css";
import React, { Component } from "react";
class App extends Component {
state = {
height: null,
open: true,
displayBlock: "",
};
constructor(props) {
super(props);
// to access the elements property offsetHeight in later lifecycle methods
this.myRef = React.createRef();
}
componentDidMount() {
this.myRef.current.addEventListener("transitionEnd", function () {
this.setState({ displayBlock: "" });
this.setState({ height: null });
});
}
toggle = () => {
let content = this.myRef.current;
let wasOpen = this.state.open;
this.setState({ open: !wasOpen });
// STEP 2.1
// This *would* set class to ensure display:block is set (to ensure it is visible)
// ISSUE 1: however in React setState is asynchronous and this it won't be rendered in time to be useful for the next change a couple of lines later!
this.setState({ displayBlock: true });
// STEP 2.2
// *would* now be able to get the actual full natural height
//(ISSUE 1: in React the above change has not rendered in time for this to get what we need!)
let fullHeight = content.offsetHeight;
if (wasOpen) {
// STEP 3
// This *would* set initial height to the the required absolute height value (fullHeight)
// ISSUE 2: Even if we had the fullHeight value from above - setting it won't ...
// ... take effect in time for the next steps
this.setState({ height: fullHeight });
// STEP 4
// I *would* have now ensured the elements height updated immedietly, by using following line
// ISSUE 3.1: but the above setState heights will not have updated the elements yet anyway
// ... content.offsetWidth;
// STEP 5
// This *would* set final height to the the required absolute height value (0px)
// ... and thus browser *would* detect the change triggering the transition
// ISSUE 3.2: but the above setState heights will not have updated the elements yet anyway
// ... thus when React renders the element - browser only sees change from height: auto to finalHeight
// ... transitions dont work with auto values ... so no transition!
this.setState({ height: "0px" });
// STEP 6
// ISSUE 4
// If transition happens at this point (which is does not in React - because of the above issues)
// ... transitionEnd event handler (above) would ...
// 1) remove any explicit height so it returns to auto
// ... this.setState({ height: null });
// 2) remove class "block" which was only for ensuring visibilty whilst height is in transition ...
// ... this.setState({ displayBlock: null });
} else {
// (Similar steps as other part of IF block) ...
// *would* set initial height to the the required intial absolute height value (0px)
this.setState({ height: "0px" });
// the setState above has not changed the elements style yet ...
// ... so the below wont trigger transition
this.setState({ height: fullHeight });
// If transition happens at this point (which is does not in React - because of the above issues)
// ... transitionEnd event handler (above) would ...
// 1) remove any explicit height so it returns to auto
// ... this.setState({ height: null });
// 2) remove class "block" which was only for ensuring visibilty whilst height is in transition ...
// ... this.setState({ displayBlock: null });
}
};
render() {
let innerStyle = { height: this.state.height };
let classes = `content ${this.state.displayBlock ? "block" : ""} ${
this.state.open ? "open" : ""
}`;
return (
<React.Fragment>
<button className="button" onClick={this.toggle}>
Toggle
</button>
<div className={classes} style={innerStyle} ref={this.myRef}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum.
</div>
</React.Fragment>
);
}
}
export default App;
解决方案
我想你正在寻找ResizeObserver
https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver
componentDidMount() {
let div = document.querySelector(id); // <--- pass element id to observe
this.resizeObserver.observe(div);
}
resizeObserver = new ResizeObserver(entries => {
for (let entry of entries) {
let width = entry.contentRect.width;
this.setState({ width: width });
}
});
推荐阅读
- python-3.x - 气流记录 BrokenPipeException
- javascript - FlatList renderItem 被多次调用
- java - 一台计算机损坏 Excel 文件(通过我的程序通过 Apache POI 创建),而其他计算机工作正常
- android - 如何使用 sqlite 数据设置多个警报?
- r - 带有 html 标签的 rhandsontable
- go - OpenShift API - 无法使用配置
- css - Flexbox 有问题 2 列 3 项
- java - 拖放后被拖动的物品消失
- python - Wagtail:为 Multisite 中的每个应用程序设置一个专用的静态文件夹
- php - 处理具有相同错误号的 Mysql 错误