サポートチャット
右下の起動ボタンから展開するミニチャット窓です。ヘッダー・履歴・入力欄がそろい、閉じるボタンでまた起動ボタンへ最小化します。
ライブデモ
コード
HTML
<!-- サポートチャット: 右下の起動ボタンからミニチャット窓が展開し、閉じるボタンで起動ボタンに戻る -->
<div class="stage">
<p class="csw-hint">画面右下のチャットボタンをクリックしてみてください</p>
<button type="button" class="csw-launcher" id="cswLauncher" aria-label="サポートチャットを開く" aria-expanded="false">
<span class="csw-launcher__icon" aria-hidden="true">💬</span>
<span class="csw-launcher__badge" id="cswBadge" aria-hidden="true">1</span>
</button>
<section class="csw-panel" id="cswPanel" aria-label="サポートチャット" aria-hidden="true">
<header class="csw-panel__head">
<div class="csw-panel__who">
<span class="csw-panel__avatar" aria-hidden="true">S</span>
<div>
<p class="csw-panel__title">サポートチーム</p>
<p class="csw-panel__status"><span class="csw-dot" aria-hidden="true"></span>だいたい数分で返信します</p>
</div>
</div>
<button type="button" class="csw-close" id="cswClose" aria-label="チャットを閉じる">✕</button>
</header>
<ul class="csw-history" id="cswHistory">
<li class="csw-msg csw-msg--in">こんにちは!ご質問があればなんでもどうぞ 😊</li>
</ul>
<form class="csw-form" id="cswForm">
<input type="text" class="csw-input" id="cswInput" placeholder="メッセージを入力…" aria-label="メッセージを入力">
<button type="submit" class="csw-send" aria-label="送信"><span aria-hidden="true">➤</span></button>
</form>
</section>
</div>
CSS
/* サポートチャットウィジェット */
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 28px;
font-family: "Hiragino Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
background:
radial-gradient(circle at 12% 12%, rgba(56, 189, 248, .12) 0%, transparent 45%),
radial-gradient(circle at 88% 88%, rgba(99, 102, 241, .12) 0%, transparent 45%),
#0c1220;
color: #e6edf7;
}
.csw-hint {
max-width: 260px;
margin: 0;
text-align: center;
font-size: 13px;
line-height: 1.7;
color: rgba(230, 237, 247, .5);
}
/* 起動ボタン */
.csw-launcher {
position: fixed;
right: clamp(14px, 4vw, 26px);
bottom: clamp(14px, 4vh, 26px);
width: 56px;
height: 56px;
border-radius: 50%;
border: none;
display: grid;
place-items: center;
background: linear-gradient(135deg, #38bdf8, #6366f1);
box-shadow: 0 16px 34px -14px rgba(56, 189, 248, .65);
cursor: pointer;
transition: transform .25s cubic-bezier(.34, 1.56, .64, 1), opacity .2s ease;
z-index: 5;
}
.csw-launcher:hover { transform: translateY(-3px) scale(1.05); }
.csw-launcher.is-hidden { opacity: 0; transform: scale(.6); pointer-events: none; }
.csw-launcher__icon { font-size: 24px; }
.csw-launcher__badge {
position: absolute;
top: -2px;
right: -2px;
min-width: 18px;
height: 18px;
padding: 0 4px;
border-radius: 999px;
background: #f43f5e;
color: #fff;
font-size: 10.5px;
font-weight: 800;
display: grid;
place-items: center;
box-shadow: 0 0 0 2px #0c1220;
}
.csw-launcher__badge[hidden] { display: none; }
/* チャット窓 */
.csw-panel {
position: fixed;
right: clamp(10px, 4vw, 26px);
bottom: clamp(78px, 12vh, 96px);
width: min(320px, 88vw);
max-height: min(440px, 72vh);
display: flex;
flex-direction: column;
border-radius: 20px;
overflow: hidden;
background: #121a2b;
border: 1px solid rgba(255, 255, 255, .1);
box-shadow: 0 30px 70px -24px rgba(0, 0, 0, .8);
opacity: 0;
transform: translateY(16px) scale(.94);
transform-origin: bottom right;
pointer-events: none;
transition: opacity .25s ease, transform .3s cubic-bezier(.22, 1, .36, 1);
z-index: 6;
}
.csw-panel.is-open {
opacity: 1;
transform: translateY(0) scale(1);
pointer-events: auto;
}
.csw-panel__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 14px 14px 14px 16px;
background: linear-gradient(135deg, #1a3a5c, #17223b);
border-bottom: 1px solid rgba(255, 255, 255, .08);
}
.csw-panel__who { display: flex; align-items: center; gap: 10px; }
.csw-panel__avatar {
flex: none;
width: 34px;
height: 34px;
border-radius: 50%;
display: grid;
place-items: center;
font-weight: 800;
font-size: 13px;
color: #082032;
background: linear-gradient(135deg, #7dd3fc, #38bdf8);
}
.csw-panel__title { margin: 0; font-size: 13.5px; font-weight: 700; }
.csw-panel__status {
margin: 2px 0 0;
display: flex;
align-items: center;
gap: 5px;
font-size: 10.5px;
color: rgba(230, 237, 247, .55);
}
.csw-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #34d399;
box-shadow: 0 0 0 0 rgba(52, 211, 153, .5);
animation: csw-pulse 2.2s ease-out infinite;
}
@keyframes csw-pulse {
0% { box-shadow: 0 0 0 0 rgba(52, 211, 153, .45); }
70% { box-shadow: 0 0 0 5px rgba(52, 211, 153, 0); }
100% { box-shadow: 0 0 0 0 rgba(52, 211, 153, 0); }
}
.csw-close {
flex: none;
width: 28px;
height: 28px;
border-radius: 50%;
border: none;
background: rgba(255, 255, 255, .08);
color: #e6edf7;
font-size: 12px;
cursor: pointer;
transition: background .15s ease, transform .15s ease;
}
.csw-close:hover { background: rgba(255, 255, 255, .16); transform: rotate(90deg); }
.csw-history {
list-style: none;
margin: 0;
padding: 14px;
flex: 1;
min-height: 0;
overflow-y: auto;
display: grid;
align-content: start;
gap: 8px;
}
.csw-msg {
width: fit-content;
max-width: 84%;
padding: 9px 13px;
font-size: 12.5px;
line-height: 1.55;
border-radius: 14px;
animation: csw-pop .3s cubic-bezier(.22, 1, .36, 1);
}
.csw-msg--in {
background: rgba(255, 255, 255, .08);
border: 1px solid rgba(255, 255, 255, .07);
border-bottom-left-radius: 4px;
}
.csw-msg--out {
margin-left: auto;
background: linear-gradient(135deg, #38bdf8, #6366f1);
border-bottom-right-radius: 4px;
color: #051220;
font-weight: 600;
}
@keyframes csw-pop {
from { opacity: 0; transform: translateY(6px) scale(.97); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
/* 入力中バブル(自動返信の前に一瞬表示) */
.csw-typing {
width: fit-content;
display: inline-flex;
gap: 4px;
padding: 10px 12px;
border-radius: 14px;
border-bottom-left-radius: 4px;
background: rgba(255, 255, 255, .08);
border: 1px solid rgba(255, 255, 255, .07);
}
.csw-typing span {
width: 6px;
height: 6px;
border-radius: 50%;
background: rgba(230, 237, 247, .6);
animation: csw-bounce 1.2s ease-in-out infinite;
}
.csw-typing span:nth-child(2) { animation-delay: .15s; }
.csw-typing span:nth-child(3) { animation-delay: .3s; }
@keyframes csw-bounce {
0%, 60%, 100% { transform: translateY(0); opacity: .5; }
30% { transform: translateY(-4px); opacity: 1; }
}
.csw-form {
display: flex;
gap: 8px;
padding: 10px 12px;
border-top: 1px solid rgba(255, 255, 255, .08);
}
.csw-input {
flex: 1;
min-width: 0;
padding: 9px 13px;
font: inherit;
font-size: 12.5px;
color: #e6edf7;
background: rgba(255, 255, 255, .07);
border: 1px solid rgba(255, 255, 255, .1);
border-radius: 999px;
}
.csw-input:focus { outline: none; border-color: rgba(56, 189, 248, .55); }
.csw-input::placeholder { color: rgba(230, 237, 247, .35); }
.csw-send {
flex: none;
width: 34px;
height: 34px;
border-radius: 50%;
border: none;
display: grid;
place-items: center;
color: #fff;
background: linear-gradient(135deg, #38bdf8, #6366f1);
cursor: pointer;
transition: transform .15s cubic-bezier(.22, 1, .36, 1);
}
.csw-send:hover { transform: translateY(-2px) scale(1.05); }
.csw-send:active { transform: scale(.94); }
@media (prefers-reduced-motion: reduce) {
.csw-launcher, .csw-panel, .csw-close, .csw-msg, .csw-dot, .csw-typing span, .csw-send {
transition: none;
animation: none;
}
}
JavaScript
// サポートチャットウィジェット: 起動/最小化の切替と、送信後に入力中表示を挟んだ自動返信
(() => {
const launcher = document.getElementById('cswLauncher');
const panel = document.getElementById('cswPanel');
const badge = document.getElementById('cswBadge');
const closeBtn = document.getElementById('cswClose');
const form = document.getElementById('cswForm');
const input = document.getElementById('cswInput');
const history = document.getElementById('cswHistory');
if (!launcher || !panel || !form || !input || !history) return; // null安全
const REPLIES = [
'ありがとうございます、内容を確認して担当より折り返しますね。',
'承知しました。もう少し詳しく教えていただけますか?',
'お問い合わせありがとうございます、順番にご案内しますね。',
];
let replyIndex = 0;
const scrollToBottom = () => { history.scrollTop = history.scrollHeight; };
const open = () => {
panel.classList.add('is-open');
panel.setAttribute('aria-hidden', 'false');
launcher.classList.add('is-hidden');
launcher.setAttribute('aria-expanded', 'true');
if (badge) badge.hidden = true;
input.focus();
};
const close = () => {
panel.classList.remove('is-open');
panel.setAttribute('aria-hidden', 'true');
launcher.classList.remove('is-hidden');
launcher.setAttribute('aria-expanded', 'false');
};
launcher.addEventListener('click', open);
if (closeBtn) closeBtn.addEventListener('click', close);
form.addEventListener('submit', (e) => {
e.preventDefault();
const text = input.value.trim();
if (!text) return;
const userMsg = document.createElement('li');
userMsg.className = 'csw-msg csw-msg--out';
userMsg.textContent = text;
history.appendChild(userMsg);
input.value = '';
scrollToBottom();
const typing = document.createElement('li');
typing.className = 'csw-typing';
typing.setAttribute('role', 'status');
typing.setAttribute('aria-label', 'サポートが入力中です');
typing.innerHTML = '<span></span><span></span><span></span>';
history.appendChild(typing);
scrollToBottom();
setTimeout(() => {
typing.remove();
const reply = document.createElement('li');
reply.className = 'csw-msg csw-msg--in';
reply.textContent = REPLIES[replyIndex % REPLIES.length];
replyIndex += 1;
history.appendChild(reply);
scrollToBottom();
}, 1100);
});
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「サポートチャット」の効果を追加してください。
# 追加してほしい効果
サポートチャット(チャット & メディア)
右下の起動ボタンから展開するミニチャット窓です。ヘッダー・履歴・入力欄がそろい、閉じるボタンでまた起動ボタンへ最小化します。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- サポートチャット: 右下の起動ボタンからミニチャット窓が展開し、閉じるボタンで起動ボタンに戻る -->
<div class="stage">
<p class="csw-hint">画面右下のチャットボタンをクリックしてみてください</p>
<button type="button" class="csw-launcher" id="cswLauncher" aria-label="サポートチャットを開く" aria-expanded="false">
<span class="csw-launcher__icon" aria-hidden="true">💬</span>
<span class="csw-launcher__badge" id="cswBadge" aria-hidden="true">1</span>
</button>
<section class="csw-panel" id="cswPanel" aria-label="サポートチャット" aria-hidden="true">
<header class="csw-panel__head">
<div class="csw-panel__who">
<span class="csw-panel__avatar" aria-hidden="true">S</span>
<div>
<p class="csw-panel__title">サポートチーム</p>
<p class="csw-panel__status"><span class="csw-dot" aria-hidden="true"></span>だいたい数分で返信します</p>
</div>
</div>
<button type="button" class="csw-close" id="cswClose" aria-label="チャットを閉じる">✕</button>
</header>
<ul class="csw-history" id="cswHistory">
<li class="csw-msg csw-msg--in">こんにちは!ご質問があればなんでもどうぞ 😊</li>
</ul>
<form class="csw-form" id="cswForm">
<input type="text" class="csw-input" id="cswInput" placeholder="メッセージを入力…" aria-label="メッセージを入力">
<button type="submit" class="csw-send" aria-label="送信"><span aria-hidden="true">➤</span></button>
</form>
</section>
</div>
【CSS】
/* サポートチャットウィジェット */
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 28px;
font-family: "Hiragino Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
background:
radial-gradient(circle at 12% 12%, rgba(56, 189, 248, .12) 0%, transparent 45%),
radial-gradient(circle at 88% 88%, rgba(99, 102, 241, .12) 0%, transparent 45%),
#0c1220;
color: #e6edf7;
}
.csw-hint {
max-width: 260px;
margin: 0;
text-align: center;
font-size: 13px;
line-height: 1.7;
color: rgba(230, 237, 247, .5);
}
/* 起動ボタン */
.csw-launcher {
position: fixed;
right: clamp(14px, 4vw, 26px);
bottom: clamp(14px, 4vh, 26px);
width: 56px;
height: 56px;
border-radius: 50%;
border: none;
display: grid;
place-items: center;
background: linear-gradient(135deg, #38bdf8, #6366f1);
box-shadow: 0 16px 34px -14px rgba(56, 189, 248, .65);
cursor: pointer;
transition: transform .25s cubic-bezier(.34, 1.56, .64, 1), opacity .2s ease;
z-index: 5;
}
.csw-launcher:hover { transform: translateY(-3px) scale(1.05); }
.csw-launcher.is-hidden { opacity: 0; transform: scale(.6); pointer-events: none; }
.csw-launcher__icon { font-size: 24px; }
.csw-launcher__badge {
position: absolute;
top: -2px;
right: -2px;
min-width: 18px;
height: 18px;
padding: 0 4px;
border-radius: 999px;
background: #f43f5e;
color: #fff;
font-size: 10.5px;
font-weight: 800;
display: grid;
place-items: center;
box-shadow: 0 0 0 2px #0c1220;
}
.csw-launcher__badge[hidden] { display: none; }
/* チャット窓 */
.csw-panel {
position: fixed;
right: clamp(10px, 4vw, 26px);
bottom: clamp(78px, 12vh, 96px);
width: min(320px, 88vw);
max-height: min(440px, 72vh);
display: flex;
flex-direction: column;
border-radius: 20px;
overflow: hidden;
background: #121a2b;
border: 1px solid rgba(255, 255, 255, .1);
box-shadow: 0 30px 70px -24px rgba(0, 0, 0, .8);
opacity: 0;
transform: translateY(16px) scale(.94);
transform-origin: bottom right;
pointer-events: none;
transition: opacity .25s ease, transform .3s cubic-bezier(.22, 1, .36, 1);
z-index: 6;
}
.csw-panel.is-open {
opacity: 1;
transform: translateY(0) scale(1);
pointer-events: auto;
}
.csw-panel__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 14px 14px 14px 16px;
background: linear-gradient(135deg, #1a3a5c, #17223b);
border-bottom: 1px solid rgba(255, 255, 255, .08);
}
.csw-panel__who { display: flex; align-items: center; gap: 10px; }
.csw-panel__avatar {
flex: none;
width: 34px;
height: 34px;
border-radius: 50%;
display: grid;
place-items: center;
font-weight: 800;
font-size: 13px;
color: #082032;
background: linear-gradient(135deg, #7dd3fc, #38bdf8);
}
.csw-panel__title { margin: 0; font-size: 13.5px; font-weight: 700; }
.csw-panel__status {
margin: 2px 0 0;
display: flex;
align-items: center;
gap: 5px;
font-size: 10.5px;
color: rgba(230, 237, 247, .55);
}
.csw-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #34d399;
box-shadow: 0 0 0 0 rgba(52, 211, 153, .5);
animation: csw-pulse 2.2s ease-out infinite;
}
@keyframes csw-pulse {
0% { box-shadow: 0 0 0 0 rgba(52, 211, 153, .45); }
70% { box-shadow: 0 0 0 5px rgba(52, 211, 153, 0); }
100% { box-shadow: 0 0 0 0 rgba(52, 211, 153, 0); }
}
.csw-close {
flex: none;
width: 28px;
height: 28px;
border-radius: 50%;
border: none;
background: rgba(255, 255, 255, .08);
color: #e6edf7;
font-size: 12px;
cursor: pointer;
transition: background .15s ease, transform .15s ease;
}
.csw-close:hover { background: rgba(255, 255, 255, .16); transform: rotate(90deg); }
.csw-history {
list-style: none;
margin: 0;
padding: 14px;
flex: 1;
min-height: 0;
overflow-y: auto;
display: grid;
align-content: start;
gap: 8px;
}
.csw-msg {
width: fit-content;
max-width: 84%;
padding: 9px 13px;
font-size: 12.5px;
line-height: 1.55;
border-radius: 14px;
animation: csw-pop .3s cubic-bezier(.22, 1, .36, 1);
}
.csw-msg--in {
background: rgba(255, 255, 255, .08);
border: 1px solid rgba(255, 255, 255, .07);
border-bottom-left-radius: 4px;
}
.csw-msg--out {
margin-left: auto;
background: linear-gradient(135deg, #38bdf8, #6366f1);
border-bottom-right-radius: 4px;
color: #051220;
font-weight: 600;
}
@keyframes csw-pop {
from { opacity: 0; transform: translateY(6px) scale(.97); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
/* 入力中バブル(自動返信の前に一瞬表示) */
.csw-typing {
width: fit-content;
display: inline-flex;
gap: 4px;
padding: 10px 12px;
border-radius: 14px;
border-bottom-left-radius: 4px;
background: rgba(255, 255, 255, .08);
border: 1px solid rgba(255, 255, 255, .07);
}
.csw-typing span {
width: 6px;
height: 6px;
border-radius: 50%;
background: rgba(230, 237, 247, .6);
animation: csw-bounce 1.2s ease-in-out infinite;
}
.csw-typing span:nth-child(2) { animation-delay: .15s; }
.csw-typing span:nth-child(3) { animation-delay: .3s; }
@keyframes csw-bounce {
0%, 60%, 100% { transform: translateY(0); opacity: .5; }
30% { transform: translateY(-4px); opacity: 1; }
}
.csw-form {
display: flex;
gap: 8px;
padding: 10px 12px;
border-top: 1px solid rgba(255, 255, 255, .08);
}
.csw-input {
flex: 1;
min-width: 0;
padding: 9px 13px;
font: inherit;
font-size: 12.5px;
color: #e6edf7;
background: rgba(255, 255, 255, .07);
border: 1px solid rgba(255, 255, 255, .1);
border-radius: 999px;
}
.csw-input:focus { outline: none; border-color: rgba(56, 189, 248, .55); }
.csw-input::placeholder { color: rgba(230, 237, 247, .35); }
.csw-send {
flex: none;
width: 34px;
height: 34px;
border-radius: 50%;
border: none;
display: grid;
place-items: center;
color: #fff;
background: linear-gradient(135deg, #38bdf8, #6366f1);
cursor: pointer;
transition: transform .15s cubic-bezier(.22, 1, .36, 1);
}
.csw-send:hover { transform: translateY(-2px) scale(1.05); }
.csw-send:active { transform: scale(.94); }
@media (prefers-reduced-motion: reduce) {
.csw-launcher, .csw-panel, .csw-close, .csw-msg, .csw-dot, .csw-typing span, .csw-send {
transition: none;
animation: none;
}
}
【JavaScript】
// サポートチャットウィジェット: 起動/最小化の切替と、送信後に入力中表示を挟んだ自動返信
(() => {
const launcher = document.getElementById('cswLauncher');
const panel = document.getElementById('cswPanel');
const badge = document.getElementById('cswBadge');
const closeBtn = document.getElementById('cswClose');
const form = document.getElementById('cswForm');
const input = document.getElementById('cswInput');
const history = document.getElementById('cswHistory');
if (!launcher || !panel || !form || !input || !history) return; // null安全
const REPLIES = [
'ありがとうございます、内容を確認して担当より折り返しますね。',
'承知しました。もう少し詳しく教えていただけますか?',
'お問い合わせありがとうございます、順番にご案内しますね。',
];
let replyIndex = 0;
const scrollToBottom = () => { history.scrollTop = history.scrollHeight; };
const open = () => {
panel.classList.add('is-open');
panel.setAttribute('aria-hidden', 'false');
launcher.classList.add('is-hidden');
launcher.setAttribute('aria-expanded', 'true');
if (badge) badge.hidden = true;
input.focus();
};
const close = () => {
panel.classList.remove('is-open');
panel.setAttribute('aria-hidden', 'true');
launcher.classList.remove('is-hidden');
launcher.setAttribute('aria-expanded', 'false');
};
launcher.addEventListener('click', open);
if (closeBtn) closeBtn.addEventListener('click', close);
form.addEventListener('submit', (e) => {
e.preventDefault();
const text = input.value.trim();
if (!text) return;
const userMsg = document.createElement('li');
userMsg.className = 'csw-msg csw-msg--out';
userMsg.textContent = text;
history.appendChild(userMsg);
input.value = '';
scrollToBottom();
const typing = document.createElement('li');
typing.className = 'csw-typing';
typing.setAttribute('role', 'status');
typing.setAttribute('aria-label', 'サポートが入力中です');
typing.innerHTML = '<span></span><span></span><span></span>';
history.appendChild(typing);
scrollToBottom();
setTimeout(() => {
typing.remove();
const reply = document.createElement('li');
reply.className = 'csw-msg csw-msg--in';
reply.textContent = REPLIES[replyIndex % REPLIES.length];
replyIndex += 1;
history.appendChild(reply);
scrollToBottom();
}, 1100);
});
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。