实现帖子省略号菜单的互斥显示与滚动隐藏功能
实现帖子省略号菜单的互斥显示与滚动隐藏功能
我明白你需要给帖子列表实现一个互斥的省略号下拉菜单,同时滚动时自动隐藏菜单。下面我会帮你修改代码来实现这些需求:
需求回顾
- 点击指定帖子的省略号,仅该帖子的下拉菜单展开
- 切换点击其他帖子的省略号时,自动关闭之前打开的菜单,仅保留当前菜单显示
- 全局始终最多只有一个下拉菜单处于打开状态
- 页面滚动时,自动隐藏所有打开的下拉菜单
解决方案代码
我们需要在生成帖子的逻辑中添加省略号按钮和下拉菜单,同时绑定事件处理逻辑来实现互斥显示和滚动隐藏:
// 全局变量:跟踪当前打开的下拉菜单 let currentOpenMenu = null; // 页面滚动时关闭所有打开的菜单 window.addEventListener('scroll', () => { if (currentOpenMenu) { currentOpenMenu.style.display = 'none'; currentOpenMenu = null; } }); // 点击页面空白区域关闭菜单 document.addEventListener('click', () => { if (currentOpenMenu) { currentOpenMenu.style.display = 'none'; currentOpenMenu = null; } }); function fetchPosts() { if (user != "") { const postsRef = database.ref("posts/"); postsRef.once("value", (snapshot) => { const postsContainer = document.getElementById("posts"); postsContainer.innerHTML = ""; // Clear existing posts // Collect posts into an array const postsArray = []; snapshot.forEach((postSnapshot) => { const post = postSnapshot.val(); post.id = postSnapshot.key; // Add the post ID to the object postsArray.push(post); }); // Sort posts by date (newest to oldest) postsArray.sort((a, b) => b.time - a.time); // Loop through the sorted posts postsArray.forEach((post) => { // Create elements for the post data const postDiv = document.createElement("div"); postDiv.classList.add("post"); postDiv.setAttribute("id", post.id); // 给帖子容器添加相对定位,让菜单可以绝对定位在右上角 postDiv.style.position = 'relative'; // --- 保留你原有的postDiv内部元素生成逻辑:用户头像、标题、用户名、时间、描述等 --- const user_pic = document.createElement("div"); user_pic.classList.add("user-pic"); const img = document.createElement("img"); const img_address = database.ref("users/" + post.user_id + "/user_pic"); img_address.on("value", (snapshot) => { const data = snapshot.val(); img.src = data; }); user_pic.appendChild(img); const post_content = document.createElement("div"); const row = document.createElement("div"); post_content.classList.add("post_content"); row.classList.add("row"); post_content.appendChild(row); const title = document.createElement("p"); title.classList.add("title"); title.innerText = post.title; const usernameAndTime = document.createElement("div"); const username = document.createElement("p"); username.classList.add("username"); const f_name = database.ref("users/" + post.user_id + "/full_name"); f_name.on("value", (snapshot) => { const data = snapshot.val(); username.innerText = data; }); usernameAndTime.appendChild(username) const description = document.createElement("p"); description.classList.add("description"); description.innerText = post.description; const time = document.createElement("p"); time.classList.add("time"); const dateString = new Date(post.time).toString(); let date = new Date(dateString); time.innerText = formatDate(date); usernameAndTime.appendChild(time); usernameAndTime.classList.add("usernameAndTime"); row.appendChild(usernameAndTime); post_content.appendChild(description); postDiv.appendChild(user_pic); postDiv.appendChild(post_content); // ====================== 新增:省略号按钮与下拉菜单 ====================== // 1. 创建省略号按钮 const ellipsisBtn = document.createElement("button"); ellipsisBtn.classList.add("ellipsis-btn"); ellipsisBtn.innerHTML = "⋮"; ellipsisBtn.style.cssText = ` position: absolute; top: 10px; right: 10px; background: transparent; border: none; font-size: 20px; cursor: pointer; color: #666; padding: 4px; `; // 2. 创建下拉菜单容器 const dropdownMenu = document.createElement("div"); dropdownMenu.classList.add("post-dropdown"); dropdownMenu.style.cssText = ` display: none; position: absolute; top: 40px; right: 10px; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.15); padding: 8px 0; width: 120px; z-index: 100; `; // 3. 添加菜单选项 const editOption = document.createElement("div"); editOption.classList.add("dropdown-option"); editOption.textContent = "编辑"; editOption.addEventListener('click', () => { console.log(`开始编辑帖子:${post.id}`); dropdownMenu.style.display = 'none'; currentOpenMenu = null; // 可在这里添加编辑帖子的具体逻辑 }); const deleteOption = document.createElement("div"); deleteOption.classList.add("dropdown-option"); deleteOption.textContent = "删除"; deleteOption.style.color = "#ff4444"; deleteOption.addEventListener('click', () => { if (confirm('确定要删除这篇帖子吗?')) { database.ref(`posts/${post.id}`).remove() .then(() => fetchPosts()) .catch(err => console.error('删除失败:', err)); } dropdownMenu.style.display = 'none'; currentOpenMenu = null; }); // 给菜单选项添加hover样式 [editOption, deleteOption].forEach(option => { option.style.cssText = ` padding: 8px 16px; cursor: pointer; font-size: 14px; `; option.addEventListener('mouseenter', () => option.style.backgroundColor = '#f5f5f5'); option.addEventListener('mouseleave', () => option.style.backgroundColor = 'transparent'); }); dropdownMenu.appendChild(editOption); dropdownMenu.appendChild(deleteOption); // 4. 绑定省略号按钮点击事件 ellipsisBtn.addEventListener('click', (e) => { e.stopPropagation(); // 阻止事件冒泡,避免触发页面空白区域的关闭逻辑 // 互斥逻辑:关闭其他已打开的菜单 if (currentOpenMenu && currentOpenMenu !== dropdownMenu) { currentOpenMenu.style.display = 'none'; } // 切换当前菜单的显示状态 if (dropdownMenu.style.display === 'block') { dropdownMenu.style.display = 'none'; currentOpenMenu = null; } else { dropdownMenu.style.display = 'block'; currentOpenMenu = dropdownMenu; } }); // 将按钮和菜单添加到帖子容器 postDiv.appendChild(ellipsisBtn); postDiv.appendChild(dropdownMenu); // --- 保留你原有的帖子图片、responseBar生成逻辑 --- const postImg = database.ref("posts/" + post.id + "/images"); postImg.on("value", (snapshot) => { const data = snapshot.val(); const master_post = document.createElement("div"); master_post.appendChild(postDiv); master_post.classList.add("master_post"); const responseBar = document.createElement("div"); responseBar.classList.add("responseBar"); // 原有的点赞、分享、评论、收藏按钮逻辑 const likes = document.createElement("div"); likes.classList.add("responseItem"); const likesIcon = document.createElement("img"); likesIcon.classList.add("responseIcon"); const likesCount = document.createElement("p"); likesCount.classList.add("likesCount"); likesCount.innerText = post.likes; likes.onclick = function() { toggleLike(post.id); }; const likesRef = database.ref("posts/" + post.id); likesRef.on("value", (snapshot) => { const data = snapshot.val(); const likes = data.likes || 0; const likedBy = data.liked_by || []; likesIcon.src = likedBy.includes(user) ? "/assets/heart-filled.svg" : "/assets/heart-outline.svg"; }); likes.appendChild(likesIcon); likes.appendChild(likesCount); responseBar.appendChild(likes); const share = document.createElement("div"); share.classList.add("responseItem"); const shareIcon = document.createElement("img"); shareIcon.src = "assets/share.svg" shareIcon.classList.add("responseIcon"); const shareCount = document.createElement("p"); shareCount.classList.add("shareCount"); shareCount.innerText = 0 share.appendChild(shareIcon); share.appendChild(shareCount); responseBar.appendChild(share); const comments = document.createElement("div"); comments.classList.add("responseItem"); const commentsIcon = document.createElement("img"); commentsIcon.src = "assets/comments.svg" commentsIcon.classList.add("responseIcon"); const commentsCount = document.createElement("p"); commentsCount.classList.add("commentsCount"); commentsCount.innerText = 0; comments.appendChild(commentsIcon); comments.appendChild(commentsCount); responseBar.appendChild(comments); const bookmark = document.createElement("div"); bookmark.classList.add("responseItem"); const bookmarkIcon = document.createElement("img"); const bookmarkCount = document.createElement("p"); bookmarkIcon.src = "assets/bookmark.svg"; bookmarkCount.innerText = 0; bookmarkIcon.classList.add("responseIcon"); bookmarkCount.classList.add("commentsCount"); bookmark.appendChild(bookmarkIcon); bookmark.appendChild(bookmarkCount); responseBar.appendChild(bookmark); master_post.appendChild(responseBar); // 处理帖子图片 if (data != "") { const imgPostDiv = document.createElement("div"); const imgPic = document.createElement("img"); imgPostDiv.appendChild(imgPic); imgPostDiv.style.display = "block"; imgPic.src = data; post_content.appendChild(imgPostDiv); imgPostDiv.classList.add("post-image"); } postsContainer.appendChild(master_post); }); }); }); } } // 原有的日期格式化函数(假设你已实现) function formatDate(date) { return date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); }
关键逻辑说明
- 全局菜单跟踪:用
currentOpenMenu变量记录当前打开的菜单,确保全局最多只有一个菜单处于激活状态 - 互斥显示处理:点击新省略号时自动关闭之前的菜单,再切换当前菜单的显示状态
- 多场景关闭触发:支持滚动页面、点击空白区域、点击菜单选项三种关闭菜单的方式
- 事件冒泡控制:通过
e.stopPropagation()避免省略号点击事件触发空白区域的关闭逻辑
样式优化建议
你可以把内联样式提取到CSS文件中,让代码更整洁:
.ellipsis-btn:hover { background-color: #f0f0f0; border-radius: 4px; } .post-dropdown { display: none; position: absolute; top: 40px; right: 10px; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.15); padding: 8px 0; width: 120px; z-index: 100; } .dropdown-option { padding: 8px 16px; cursor: pointer; font-size: 14px; } .dropdown-option:hover { background-color: #f5f5f5; }
备注:内容来源于stack exchange,提问作者Sai Santosh Pal
相关产品推荐
相关产品推荐

