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

实现帖子省略号菜单的互斥显示与滚动隐藏功能

实现帖子省略号菜单的互斥显示与滚动隐藏功能

我明白你需要给帖子列表实现一个互斥的省略号下拉菜单,同时滚动时自动隐藏菜单。下面我会帮你修改代码来实现这些需求:

需求回顾

  • 点击指定帖子的省略号,仅该帖子的下拉菜单展开
  • 切换点击其他帖子的省略号时,自动关闭之前打开的菜单,仅保留当前菜单显示
  • 全局始终最多只有一个下拉菜单处于打开状态
  • 页面滚动时,自动隐藏所有打开的下拉菜单

解决方案代码

我们需要在生成帖子的逻辑中添加省略号按钮和下拉菜单,同时绑定事件处理逻辑来实现互斥显示和滚动隐藏:

// 全局变量:跟踪当前打开的下拉菜单
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'
  });
}

关键逻辑说明

  1. 全局菜单跟踪:用currentOpenMenu变量记录当前打开的菜单,确保全局最多只有一个菜单处于激活状态
  2. 互斥显示处理:点击新省略号时自动关闭之前的菜单,再切换当前菜单的显示状态
  3. 多场景关闭触发:支持滚动页面、点击空白区域、点击菜单选项三种关闭菜单的方式
  4. 事件冒泡控制:通过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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 13:13:01