React中transitionend事件绑定的函数为何重复执行两次及解决方案
transitionend 事件重复触发的根本成因
- 核心机制层面:
transitionend事件的触发粒度为单个完成过渡的CSS属性,而非元素的整轮过渡动画。只要元素配置的transition规则中存在多个属性同时参与动画,每一个属性过渡完成时都会独立触发一次事件。 - 折叠组件的常见设计误区:大部分实现可折叠区块的代码会同时为
height、opacity、纵向内边距配置同节奏的过渡效果,这类配置天然会触发多次transitionend,哪怕开发者主观上只关注高度属性的变化,其他属性的过渡完成依然会被监听器捕获。 - 隐性触发场景:如果组件使用了
box-sizing: border-box、自动高度计算的逻辑,布局偏移带来的隐式属性变化(如内容溢出导致的滚动条出现、边距的隐式过渡)也会触发额外的事件回调。
单次获取实际高度的解决方案
- 事件属性校验过滤:在
settingHeightForTransition回调内通过事件对象的propertyName字段判断触发来源,仅当属性名匹配目标监听项(如height、max-height)时才执行高度取值逻辑,其他属性触发的事件直接拦截不处理,示例代码如下:const settingHeightForTransition = (e) => { if (e.propertyName !== 'height') return; // 执行divRef绑定DOM的高度读取逻辑 } - 执行锁标记兜底:通过React的
useRef存储一次性执行标记,回调首次执行完成后置为锁定状态,后续所有触发的事件直接返回;每一轮新的折叠/展开过渡开始前,再将标记重置为可执行状态,完全避免重复执行业务逻辑。 - 精简过渡配置:从源头上缩小
transition规则覆盖的属性范围,仅保留需要做动画的高度相关属性,移除无关属性的过渡配置,从根本上减少事件触发次数,适合结构简单的折叠组件场景。 - 生命周期清理:在组件卸载阶段主动移除DOM节点上绑定的
transitionend监听器,避免残留的旧监听器捕获后续动画事件,引发和重复触发类似的异常表现。
内容的提问来源于stack exchange,提问作者KALITA
相关产品推荐
相关产品推荐

