NextJS/ReactJS:组件重建时阻止页面上滚的问题求助
解决React中切换Markdown编辑模式时页面自动上滚的问题
问题分析
点击评论的编辑按钮后页面自动向上滚动,核心原因有两个:
- 编辑按钮使用了
<a href="#">,点击时会触发浏览器默认的锚点跳转行为,直接将页面拉回顶部 - 之前尝试的
scrollIntoView未正确触发,因为缺少对edit_mode状态变化的监听逻辑
解决方案
1. 阻止链接的默认跳转行为
<a href="#">的默认行为是跳转到页面顶部,这是导致页面上滚的首要原因。修改编辑按钮的点击事件,阻止默认跳转:
<li><a className="dropdown-item" href="#" onClick={(e) => { e.preventDefault(); // 阻止默认锚点跳转 set_edit_mode(!edit_mode); }}>Edit</a></li>
同时给删除按钮也加上同样的处理,避免误触跳转:
<li><a className="dropdown-item" href="#" onClick={(e) => { e.preventDefault(); if (window.confirm("Are you sure you want to delete this comment?")) { delete_comment(comment.id) } }}>Delete</a></li>
2. 监听编辑状态变化,触发滚动定位
在CommentHTML组件中添加useEffect,监听edit_mode的状态变化,当切换到编辑模式时,让编辑器所在元素滚动到视图内:
// 在CommentHTML组件顶部新增ref和useEffect const editor_container_ref = useRef<HTMLTableCellElement>(null); useEffect(() => { if (edit_mode && editor_container_ref.current) { editor_container_ref.current.scrollIntoView({ behavior: 'smooth', // 可选:开启平滑滚动 block: 'nearest' // 让元素保持在视图内,避免过度滚动 }); } }, [edit_mode]); // 依赖项设为edit_mode,状态变化时自动触发
然后将编辑器所在的<td>绑定这个ref:
<tr> <td colSpan={4} ref={editor_container_ref}> <MarkdownEditor orig_value={comment.comment} md_type={"comment"} id={comment.id} set_edit_mode={set_edit_mode} action_ref={labels_ref}/> </td> </tr>
3. 可选优化:编辑器自动聚焦
如果需要编辑模式下编辑器自动获取焦点,可在MarkdownEditor组件中添加ref并触发聚焦:
export function MarkdownEditor({orig_value, id, md_type, set_edit_mode, action_ref}: MDProps) { const [value, setValue] = useState(orig_value); const editor_ref = useRef<any>(null); useEffect(() => { editor_ref.current?.focus(); }, []); return ( <div> <MDEditor ref={editor_ref} value={value} onChange={setValue} /> </div> ); }
修改后的完整CommentHTML组件示例
import { useEffect, useRef, useState } from 'react'; function CommentHTML({comment, labels}: CommentHTMLInput){ const [edit_mode, set_edit_mode] = useState(false); const [value, set_value] = useState(comment.comment); const markdown_ref = useRef<HTMLTableCellElement>(null); const labels_ref = useRef<HTMLSelectElement>(null); const editor_container_ref = useRef<HTMLTableCellElement>(null); render_markdown(comment.comment, markdown_ref); useEffect(() => { if (edit_mode && editor_container_ref.current) { editor_container_ref.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } }, [edit_mode]); return ( <React.Fragment > <tr className="table-group-divider"> <td>{comment.date.toString()}</td> <td>{comment.opened_by_name}</td> <td>{comment.action.name}</td> <td> <div className="dropdown"> <button className="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false"> <i className="bi bi-list"></i> </button> <ul className="dropdown-menu"> <li><a className="dropdown-item" href="#" onClick={(e) => { e.preventDefault(); set_edit_mode(!edit_mode); }}>Edit</a></li> <li><a className="dropdown-item" href="#" onClick={(e) => { e.preventDefault(); if (window.confirm("Are you sure you want to delete this comment?")) { delete_comment(comment.id) } }}>Delete</a></li> </ul> </div> </td> </tr> {edit_mode ? <React.Fragment> <tr> <td> <select ref={labels_ref} className="form-select" aria-label="Action"> {labels.map((label: FIRAction) => { if (label.name == comment.action.name){ return (<option key={"label-" + label.id} defaultValue={label.name}>{label.name}</option>) } else { return (<option key={"label-" + label.id} value={label.name}>{label.name}</option>) } })} </select> </td> </tr> <tr> <td colSpan={4} ref={editor_container_ref}> <MarkdownEditor orig_value={comment.comment} md_type={"comment"} id={comment.id} set_edit_mode={set_edit_mode} action_ref={labels_ref}/> </td> </tr> </React.Fragment> : <tr> <td className="remark-markdown" ref={markdown_ref} colSpan={4} >{comment.comment}</td> </tr> } </React.Fragment> ) }
为什么之前的尝试没生效?
<a href="#">的默认跳转优先级高于scrollIntoView,即使调用了滚动逻辑,也会被跳转行为覆盖- 之前的
useEffect未触发,大概率是因为没有将edit_mode加入依赖数组,导致仅在组件挂载时执行一次,状态变化时不会重新运行
内容的提问来源于stack exchange,提问作者Wealot
相关产品推荐
相关产品推荐

