打开URL时如何在React中滚动到锚点

7

假设我有一个组件“发布(Post)”,其中包含多个组件“评论(Comment)”。我希望在进入以下URL时,应用程序可以滚动到该锚点的评论位置:

/post/:postId/#commentId

我已经有了工作中的postId路由/post/:postId

我尝试使用React-hash-link npm包来实现它,但它并没有按照预期工作。

每个评论都有自己的ID,这是在组件上设置的,像这样:

<div class="post">
   <div class="post-header">
      <div class="post-header-avatar">
        SOME TEXT
      </div>
      <div class="post-header-info">
        SOME TEXT
      </div>
   </div>
   <div class="post-content">
      <span>POST CONTENT</span>
   </div>
   <div class="post-likers-container">
      <div class="post-likers-header label">People who like this post</div>
      <div class="post-likers">
          SOME TEXT
      </div>
   </div>
   <div class="post-comments">
      <div class="comments ">
         <div class="comments-all label">Comments</div>
         <div class="comments">
            <div class="comment" id="5d27759edd51be1858f6b6f2">
               <div class="comment-content">
               COMMENT 1 TEXT
               </div>
            </div>
            <div class="comment" id="5d2775b2dd51be1858f6b720">
               <div class="comment-content">
               COMMENT 2 TEXT
               </div>
            </div>
            <div class="comment" id="5d2775ecdd51be1858f6b753">
               <div class="comment-content">
                COMMENT 3 TEXT
               </div>
            </div>
         </div>
      </div>
   </div>
</div>

例如,如果我打开以下URL:

/post/postId/#5d2775ecdd51be1858f6b753 

我希望打开文章页面后,能自动滚动到评论区域,使用#锚点实现。

有没有什么方法可以实现这个功能?


可能是ReactJS如何滚动到元素的重复问题。 - Joe Lloyd
在你的实际代码中,你是如何生成注释的?可能是通过.map(),还是硬编码的? - Chris Ngo
我正在使用列表迭代生成注释,并将它们与评论组件进行映射。 - SaltyTeemooo
@SaltyTeemooo,你对我下面的解决方案有什么问题吗 :) - Chris Ngo
5个回答

3
我成功找到了适合我的用例的简单解决方案,无需为注释创建引用、传递引用等操作。由于我的组件层次结构如下:
  1. Post --> 渲染组件 Comments
  2. Comments --> 渲染多个由 Post 传递数据的组件 Comment
Post组件中,我创建了一个函数:
scrollToComment= () => {
    let currentLocation = window.location.href;
    const hasCommentAnchor = currentLocation.includes("/#");
    if (hasCommentAnchor) {
      const anchorCommentId = `${currentLocation.substring(currentLocation.indexOf("#") + 1)}`;
      const anchorComment = document.getElementById(anchorCommentId);
      if(anchorComment){
          anchorComment.scrollIntoView({ behavior: "smooth" });
      }
    }
  }

那么我会这样呈现评论:

<Comments limit={limit} post={post} scrollToComment={this.scrollToComment} />

在“评论”中,我按照某种排序方式生成评论,如下所示:
{sortedComments.map((comment, i) => <Comment key={i} {...comment} scrollToComment={this.props.scrollToComment}/> )}

最后,在 Comment 组件中,我在 ComponentDidMount() 中执行 scrollToComment

if(this.props.scrollToComment)
    this.props.scrollToComment(this.props._id);

之后,当我访问某个URL时,在URL的哈希部分指定的评论会有流畅的滚动效果。

我尝试了@Christopher的解决方案,但并没有起作用。


3

我非常喜欢你的解决方案 @SaltyTeemooo。在其启发下,我找到了一种更简单的方式,不需要任何回调函数。

我的设置非常相似,所以我们假设我正在处理帖子和评论。

Post 中,我像这样创建评论(简化版),并传递 anchorId:

<Comments anchorId={window.location.href.slice(window.location.href.indexOf("#") + 1)} props... />

在“评论”中,我将锚点 ID 传递到 Comment.js 中。
<Comment anchorId={props.anchorId} props.../>

然后在注释中,如果当前元素是链接的元素,我会将其滚动到视图中。

import React, { useRef, useEffect } from 'react';

function Comment () {

    const comment = useRef(null); //to be able to access the current one

    useEffect(() => {
        if(props.anchorId === props.commentData.id)
        {
            comment.current.scrollIntoView({ behavior: "smooth" });
        }
    }, []) //same as ComponentDidMount

    
    return(
       <div id={props.commentData.id} ref={comment}> //here is where the ref gets set
           ...
       </div>
    )
}

这就是我需要的! - Timbokun

1
在我这个简单的情况下,没有异步内容加载,只需在页面顶部添加以下内容即可获得所需的滚动行为:
useEffect(() => {
    const href = window.location.href
    if (href.includes("#")) {
      const id = `${href.substring(href.indexOf("#") + 1)}`
      const anchor = document.getElementById(id)
      if(anchor){
          anchor.scrollIntoView({ behavior: "smooth" })
      }
    }
}, [])

顺便说一下,这是一些常见问题页面的内容,由一堆FaqEntry对象组成,每个对象都有一个问题和答案。下面的代码允许链接到单个条目,并初始化时打开答案。

export default function FaqEntry({title, history, children}) {
if(!history) console.log("OOPS, you forgot to pass history prop", title)

const  createName = title => title.toLowerCase().replace(/[^\sa-z]/g, "").replace(/\s\s*/g, "_")
const id = createName(title)

const href = window.location.href
const isCurrent = href.includes("#") && href.substring(href.indexOf("#") + 1) === id
const [open, setOpen] = useState(isCurrent)

function handleClick() {
    setOpen(!open)
    if (history && !open) {
        const pathname = window.location.pathname + "#" + id
        history.replace(pathname)
    }
}
return <div id={id} className={`faqEntry ${open ? "open" : "closed"}`}>
    <div className="question" onClick={handleClick}>{title}</div>
    <div className="answer">{children}</div>
</div>

我从React Router传递history对象,以便在不触发页面重新加载的情况下更新浏览器历史记录。


1
花费了相当长的时间,但是尝试使用这个沙盒:https://codesandbox.io/s/scrollintoview-with-refs-and-redux-b881s
这将为您提供大量有关如何使用URL参数滚动到元素的见解。
import React from "react";
import { connect } from "react-redux";
import { getPost } from "./postActions";

class Post extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      activeComment: null
    };

    this._nodes = new Map();
  }

  componentDidMount() {
    this.props.getPost(this.props.match.params.id);
    const path = window.location.href;
    const commentId = path.slice(path.indexOf("#") + 1);
    if (commentId) {
      this.setState({
        activeComment: commentId
      });
    }
  }

  componentDidUpdate(prevProps, prevState) {
    if (this.state.activeComment !== prevState.activeComment) {
      this.scrollToComment();
    }
  }

  scrollToComment = () => {
    const { activeComment } = this.state;
    const { comments } = this.props.posts.post;
    const nodes = [];
    //Array.from creates a new shallow-copy of an array from an array-like or iterable object
    Array.from(this._nodes.values()) //this._nodes.values() returns an iterable-object populated with the Map object values
      .filter(node => node != null)
      .forEach(node => {
        nodes.push(node);
      });

    const commentIndex = comments.findIndex(
      comment => comment.id == activeComment
    );

    if (nodes[commentIndex]) {
      window.scrollTo({
        behavior: "smooth",
        top: nodes[commentIndex].offsetTop
      });
    }
  };

  createCommentList = () => {
    const { post } = this.props.posts;
    const { activeComment } = this.state;

    if (post) {
      return post.comments.map((comment, index) => {
        return (
          <div
            key={comment.id}
            className={
              "comment " + (activeComment == comment.id ? "activeComment" : "")
            }
            ref={c => this._nodes.set(comment.id, c)}
          >
            {comment.text}
          </div>
        );
      });
    }
  };

  displayPost = () => {
    const { post } = this.props.posts;

    if (post) {
      return (
        <div className="post">
          <h4>{post.title}</h4>
          <p>{post.text}</p>
        </div>
      );
    }
  };

  render() {
    return (
      <div>
        <div>{this.displayPost()}</div>
        <div>{this.createCommentList()}</div>
      </div>
    );
  }
}

const mapStateToProps = state => {
  return {
    posts: state.posts
  };
};

const mapDispatchToProps = dispatch => {
  return {
    getPost: postId => {
      dispatch(getPost(postId));
    }
  };
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Post);

0

测量...

import React, { useEffect } from 'react';

const MainApp = () => {

    const MyRef = React.createRef();

    useEffect(() => { // Same like ComponentDidMount().
        scrollTo();
    })

    const scrollTo = () => {
        window.scrollTo({
            top:myRef.offsetTop, 
            behavior: "smooth" // smooth scroll.
        });   
    }

        return (
            <div ref={MyRef}>My DIV to scroll to.</div>
        )
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接