Vue.js中展开评论导致页面滚动到顶部的问题
Vue.js中展开评论导致页面滚动到顶部的问题
嘿,我仔细看了你的代码和问题描述,一下子就找到问题根源了——这事儿跟你克隆链接时丢失事件绑定有关!
问题本质
你在关闭其他展开的评论时,用了readMoreLink.cloneNode(true)来添加“more”链接,但cloneNode(true)只会复制元素的DOM结构和属性,不会复制你通过addEventListener绑定的事件监听。所以当你重新点击这个克隆出来的“more”链接时,event.preventDefault()根本没起作用,浏览器就会默认跳转到#(也就是页面顶部),这就是为什么只有重新展开之前关闭过的评论才会触发滚动问题。
快速修复(适合暂时不想重构的场景)
别再克隆带事件的元素了,把创建链接和绑定事件的逻辑抽成一个复用函数,每次需要添加链接时都调用这个函数,确保事件绑定正确:
function createReadMoreLink(message, truncated) { const readMoreLink = document.createElement("a"); readMoreLink.href = "#"; readMoreLink.textContent = "more"; readMoreLink.classList.add("read-more-link"); readMoreLink.addEventListener("click", (event) => { event.preventDefault(); // 复用你原来的点击逻辑 if (currentlyExpandedReview && currentlyExpandedReview !== message) { currentlyExpandedReview.classList.remove("expanded"); const truncatedText = truncateText( currentlyExpandedReview.dataset.fullReview || "", 100 ); currentlyExpandedReview.textContent = truncatedText; currentlyExpandedReview.appendChild(createReadMoreLink(currentlyExpandedReview, truncatedText)); } if (message.classList.contains("expanded")) { message.classList.remove("expanded"); message.textContent = truncated; message.appendChild(createReadMoreLink(message, truncated)); currentlyExpandedReview = null; } else { message.classList.add("expanded"); message.textContent = message.dataset.fullReview || message.textContent; // 这里也要重新创建readLessLink并绑定事件,避免同样的问题 const readLessLink = createReadLessLink(message, truncated); message.appendChild(readLessLink); currentlyExpandedReview = message; } }); return readMoreLink; } function createReadLessLink(message, truncated) { const readLessLink = document.createElement("a"); readLessLink.href = "#"; readLessLink.textContent = "...less"; readLessLink.classList.add("read-less-link"); readLessLink.addEventListener("click", (event) => { event.preventDefault(); message.classList.remove("expanded"); message.textContent = truncated; message.appendChild(createReadMoreLink(message, truncated)); currentlyExpandedReview = null; }); return readLessLink; } // 原来的循环里改成这样调用 if (message.textContent && message.textContent.length > 100) { // ...其他逻辑 message.appendChild(createReadMoreLink(message, truncated)); }
更推荐的Vue式解决方案(符合最佳实践)
其实你现在的写法是纯DOM操作,完全没用到Vue的数据驱动特性,这在Vue项目里很容易出问题。不如改用响应式数据来管理每个评论的展开状态,彻底告别手动操作DOM的麻烦:
<template> <div class="reviews-container"> <div v-for="(review, index) in customerReviews" :key="review.id" class="review-message" > <p> <!-- 根据展开状态切换显示内容 --> {{ isExpanded[index] ? review.fullText : truncateText(review.fullText, 100) }} <!-- 根据文本长度和状态显示对应链接 --> <a v-if="review.fullText.length > 100" @click="toggleReview(index)" :class="{ 'read-more-link': !isExpanded[index], 'read-less-link': isExpanded[index] }" > {{ isExpanded[index] ? "...less" : "more" }} </a> </p> </div> </div> </template> <script> export default { data() { return { // 假设你的评论数据来自接口,每个评论带唯一id和完整文本 customerReviews: [ { id: 1, fullText: "这里是一条超过100字的测试评论,用来验证展开收起功能是否正常工作,看看点击more和less的时候页面会不会莫名其妙滚到顶部..." }, { id: 2, fullText: "另一条测试评论,长度刚好不够100字,所以不会显示展开链接,这样可以对比效果..." } ], isExpanded: [] // 存储每个评论的展开状态,默认未展开 }; }, methods: { truncateText(text, maxLength) { if (text.length <= maxLength) return text; return text.slice(0, maxLength) + "..."; }, toggleReview(index) { // 切换当前评论状态,同时关闭其他所有展开的评论 this.isExpanded = this.isExpanded.map((_, i) => i === index ? !this.isExpanded[index] : false); } }, mounted() { // 初始化展开状态数组,长度和评论数一致 this.isExpanded = new Array(this.customerReviews.length).fill(false); } }; </script>
这种写法的优势非常明显:
- 完全由数据驱动,不需要手动操作DOM、绑定事件,从根源上避免了事件丢失、DOM不同步的问题
- 代码更简洁易读,后续维护成本低
- 天然符合Vue的设计理念,不会触发浏览器的默认跳转行为
总结
你原来的问题是DOM操作时的事件绑定丢失,导致href="#"的默认跳转行为生效。改用Vue的响应式状态管理就能彻底解决这个问题,同时让代码更规范。如果暂时不想重构,用抽函数的方式重新创建带事件的链接也能快速修复。
备注:内容来源于stack exchange,提问作者sofies
相关产品推荐
相关产品推荐

