You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 11:12:14