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
相关产品推荐
相关产品推荐

