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

Next.js App Router中Web Component聊天按钮及iframe无法显示的排查

解决Next.js App Router中Web Component聊天按钮不显示问题

问题描述

我在采用App Router的Next.js项目中,将public目录下的chatbutton.js封装为可嵌入外部网站的Web Component,期望实现点击按钮显示聊天iframe的功能,但按以下方式引入脚本后,按钮与iframe均未正常显示。

引入示例

<script>
window.embeddedChatbotConfig = {
chatId: "fUYYCegK9QYOtoGJ25Dv1",
domain: "www.chatai.com"
}
</script>
<script
src="https://www.chatai.com/chatbutton.min.js"
chatbotId="fUYYCegK9QYOtoGJ25Dv1"
domain="www.chatai.com"
defer>
</script>

原始Web Component代码(public/chatbutton.min.js)

class ChatButton extends HTMLElement {
  constructor() {
    super();

    // Create shadow DOM
    this.attachShadow({ mode: "open" });

    // Initialize the component
    this.init();
  }

  init() {
    // Create chat button
    const chatButton = document.createElement("div");
    chatButton.setAttribute("id", "chatbase-bubble-button");
    chatButton.style.position = "fixed";
    chatButton.style.bottom = "20px";
    chatButton.style.right = "20px";
...
    chatButton.appendChild(chatButtonIcon);

    // Add click event listener
    chatButton.addEventListener("click", () => this.toggleChatWindow());

    // Append chat button to shadow DOM
    this.shadowRoot.appendChild(chatButton);

    // Create chat window
    const chat = document.createElement("div");
    chat.setAttribute("id", "chatbase-bubble-window");
    chat.style.position = "fixed";
    chat.style.flexDirection = "column";
    chat.style.justifyContent = "space-between";
    chat.style.bottom = "80px";
    chat.style.zIndex = 999999999;

    // Append iframe to chat window
    chat.innerHTML = `<iframe src="https://www.chatai.com/chatbot-iframe/${this.getAttribute(
      "data-chatbotId"
    )}" width="100%" height="100%" frameborder="0"></iframe>`;

    this.shadowRoot.appendChild(chat);

    const mediaQuery = window.matchMedia("(min-width: 550px)");

    function handleChatWindowSizeChange(t) {
      t.matches && ((chat.style.height = "600px"), (chat.style.width = "400px"));
    }

    mediaQuery.addEventListener("change", handleChatWindowSizeChange);
    handleChatWindowSizeChange(mediaQuery);
  }

  toggleChatWindow() {
    const chat = this.shadowRoot.getElementById("chatbase-bubble-window");
    chat.style.display = chat.style.display === "none" ? "flex" : "none";
  }

  get CHAT_BUTTON_ICON() {
    return `
      <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
        <path d="M20 2H4C2.9 2 2 2.9 2 4V22L6 18H20C21.1 18 22 17.1 22 16V4C22 2.9 21.1 2 20 2ZM20 16H6L4 18V4H20V16Z" fill="#FFFFFF"/>
      </svg>
    `;
  }
}

customElements.define("chatbase-bubble", ChatBaseBubble);

修复步骤

1. 修正自定义元素注册类名不匹配问题

原始代码中组件类名为ChatButton,但注册时用了不存在的ChatBaseBubble,导致Web Component无法正确定义:

// 错误代码
customElements.define("chatbase-bubble", ChatBaseBubble);

// 修复后
customElements.define("chatbase-bubble", ChatButton);

2. 补全聊天按钮的关键样式

原始代码中按钮样式缺失宽度、高度、背景色等关键属性,导致按钮不可见:

// 在chatButton的样式设置中补充以下内容
chatButton.style.width = "60px";
chatButton.style.height = "60px";
chatButton.style.borderRadius = "50%";
chatButton.style.backgroundColor = "#0070f3"; // 可自定义颜色
chatButton.style.display = "flex";
chatButton.style.alignItems = "center";
chatButton.style.justifyContent = "center";
chatButton.style.cursor = "pointer";
chatButton.style.boxShadow = "0 2px 10px rgba(0,0,0,0.15)";
chatButton.style.zIndex = 999999999;

3. 正确创建按钮图标元素

原始代码中直接使用未定义的chatButtonIcon,需先创建元素并插入SVG:

// 在chatButton.appendChild(chatButtonIcon);之前添加
const chatButtonIcon = document.createElement('div');
chatButtonIcon.innerHTML = this.CHAT_BUTTON_ICON;

4. 统一chatbotId属性的获取方式

原始代码中尝试获取data-chatbotId属性,但引入脚本时使用的是chatbotId,同时也可以兼容window.embeddedChatbotConfig配置:

// 替换iframe的src生成代码
const chatId = this.getAttribute("chatbotId") || window.embeddedChatbotConfig?.chatId;
chat.innerHTML = `<iframe src="https://www.chatai.com/chatbot-iframe/${chatId}" width="100%" height="100%" frameborder="0"></iframe>`;

5. 设置聊天窗口初始隐藏状态

原始代码中聊天窗口默认是显示的,需初始设置为隐藏:

// 在创建chat元素后添加
chat.style.display = "none";

6. 完善媒体查询的响应逻辑

补充小屏幕下的聊天窗口尺寸设置:

function handleChatWindowSizeChange(t) {
  if (t.matches) {
    chat.style.height = "600px";
    chat.style.width = "400px";
    chat.style.right = "20px";
  } else {
    chat.style.height = "80vh";
    chat.style.width = "calc(100vw - 40px)";
    chat.style.left = "20px";
    chat.style.right = "20px";
  }
}

7. 确保Next.js部署后的脚本路径正确

在Next.js App Router中,public目录下的文件会被部署到根路径,所以外部网站引入时的src要确保指向正确的部署地址,比如如果你的Next.js项目部署在www.chatai.com,那么src="https://www.chatai.com/chatbutton.min.js"是正确的,但需确认文件确实存在于public目录且部署后可访问。

最终修复后的完整代码示例

class ChatButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    this.init();
  }

  init() {
    // 创建聊天按钮
    const chatButton = document.createElement("div");
    chatButton.setAttribute("id", "chatbase-bubble-button");
    chatButton.style.position = "fixed";
    chatButton.style.bottom = "20px";
    chatButton.style.right = "20px";
    chatButton.style.width = "60px";
    chatButton.style.height = "60px";
    chatButton.style.borderRadius = "50%";
    chatButton.style.backgroundColor = "#0070f3";
    chatButton.style.display = "flex";
    chatButton.style.alignItems = "center";
    chatButton.style.justifyContent = "center";
    chatButton.style.cursor = "pointer";
    chatButton.style.boxShadow = "0 2px 10px rgba(0,0,0,0.15)";
    chatButton.style.zIndex = 999999999;

    // 创建按钮图标
    const chatButtonIcon = document.createElement('div');
    chatButtonIcon.innerHTML = this.CHAT_BUTTON_ICON;
    chatButton.appendChild(chatButtonIcon);

    // 添加点击事件
    chatButton.addEventListener("click", () => this.toggleChatWindow());
    this.shadowRoot.appendChild(chatButton);

    // 创建聊天窗口
    const chat = document.createElement("div");
    chat.setAttribute("id", "chatbase-bubble-window");
    chat.style.position = "fixed";
    chat.style.flexDirection = "column";
    chat.style.justifyContent = "space-between";
    chat.style.bottom = "80px";
    chat.style.zIndex = 999999999;
    chat.style.display = "none"; // 初始隐藏

    // 获取chatId,兼容属性和全局配置
    const chatId = this.getAttribute("chatbotId") || window.embeddedChatbotConfig?.chatId;
    chat.innerHTML = `<iframe src="https://www.chatai.com/chatbot-iframe/${chatId}" width="100%" height="100%" frameborder="0"></iframe>`;
    this.shadowRoot.appendChild(chat);

    // 响应式尺寸处理
    const mediaQuery = window.matchMedia("(min-width: 550px)");
    const handleChatWindowSizeChange = (t) => {
      if (t.matches) {
        chat.style.height = "600px";
        chat.style.width = "400px";
        chat.style.right = "20px";
      } else {
        chat.style.height = "80vh";
        chat.style.width = "calc(100vw - 40px)";
        chat.style.left = "20px";
        chat.style.right = "20px";
      }
    };

    mediaQuery.addEventListener("change", handleChatWindowSizeChange);
    handleChatWindowSizeChange(mediaQuery);
  }

  toggleChatWindow() {
    const chat = this.shadowRoot.getElementById("chatbase-bubble-window");
    chat.style.display = chat.style.display === "none" ? "flex" : "none";
  }

  get CHAT_BUTTON_ICON() {
    return `
      <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
        <path d="M20 2H4C2.9 2 2 2.9 2 4V22L6 18H20C21.1 18 22 17.1 22 16V4C22 2.9 21.1 2 20 2ZM20 16H6L4 18V4H20V16Z" fill="#FFFFFF"/>
      </svg>
    `;
  }
}

customElements.define("chatbase-bubble", ChatButton);

// 自动创建组件实例(如果没有手动添加<chatbase-bubble>标签)
if (!document.querySelector('chatbase-bubble')) {
  const chatBubble = document.createElement('chatbase-bubble');
  // 传递属性
  const scriptTag = document.querySelector('script[src*="chatbutton.min.js"]');
  if (scriptTag) {
    const chatbotId = scriptTag.getAttribute('chatbotId');
    if (chatbotId) chatBubble.setAttribute('chatbotId', chatbotId);
  }
  document.body.appendChild(chatBubble);
}

内容的提问来源于stack exchange,提问作者Santiago Padilla

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 03:25:55