2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

チャット画面においてやり取りが長くなると、会話履歴が画面内に収まり切れなくなりスクロールが必要になります。自分が値を入力したときや、相手からレスポンスが返ってきたときに自動でチャット画面一番したまでスクロールする実装を本記事では行います。

作成するもの

chatscroll.gif

環境

  • React:18.3.1
  • Vite:5.3.1

ソース

ユーザーが入力したメッセージは、画面に表示されるリスト(messages)に追加されます。
また、ボットからの返信はユーザーが入力した1秒後に自動的に生成されリスト(messages)に追加されます。
メッセージリスト(messages)が更新されると useEffect で検知し、画面は自動的に最下部までスクロールします。

App.tsx
import { useState, useRef, useEffect } from 'react';
import './App.css';

interface Message {
  id: number;
  text: string;
  sender: 'user' | 'bot';
}

const ChatApp: React.FC = () => {
  // メッセージリスト
  const [messages, setMessages] = useState<Message[]>([]);
  // ユーザの入力値
  const [input, setInput] = useState<string>('');
  // メッセージ末尾の参照
  const messagesEndRef = useRef<HTMLDivElement | null>(null);
  // メッセージid
  const idCounter = useRef<number>(0);

  // メッセージを送信する関数
  const sendMessage = () => {
    if (input.trim()) {
      // ユーザーのメッセージをmessagesに追加
      const newMessage: Message = { id: idCounter.current++, text: input, sender: 'user' };
      setMessages(prevMessages => [...prevMessages, newMessage]);
      setInput('');

      // ボットからの返信をイメージ。返信内容をmessagesに追加
      setTimeout(() => {
        const botReply: Message = { id: idCounter.current++, text: `Bot返信: ${input}`, sender: 'bot' };
        setMessages(prevMessages => [...prevMessages, botReply]);
      }, 1000);
    }
  };

  // messagesリストが更新された時に最下部にスクロールする
  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [messages]);

  return (
    <div className="chat-container">
      <div className="messages">
        {/* messagesリストをmapで展開 */}
        {messages.map((message) => (
          <div key={message.id} className={`message ${message.sender}`}>
            <div className="message-content">
              {message.text}
            </div>
          </div>
        ))}
        {/* 画面下部にスクロールするためのダミー要素 */}
        <div ref={messagesEndRef} />
      </div>
      {/* 入力欄と送信ボタン */}
      <div className="input-container">
        <input 
          type="text" 
          value={input} 
          onChange={(e) => setInput(e.target.value)} 
          onKeyDown={(e) => e.key === 'Enter' && sendMessage()} 
        />
        <button onClick={sendMessage}>送信</button>
      </div>
    </div>
  );
}

export default ChatApp;
App.css
App.css
body {
  padding: 0;
  margin: 0;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

.chat-container {
  display: flex;
  flex-direction: column;
  height: 95vh;
  width: 400px;
  border: 1px solid #ccc;
  margin: 0 auto;
}

.messages {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: 10px;
  overflow-y: auto;
  background-color: #f4f4f4;
}

.message {
  display: flex;
  margin-bottom: 10px;
}

.message-content {
  padding: 10px;
  border-radius: 5px;
  max-width: 70%;
}

.message.user {
  justify-content: flex-end;
}

.message.bot {
  justify-content: flex-start;
}

.message.user .message-content {
  background-color: #dcf8c6;
}

.message.bot .message-content {
  background-color: #fff;
}

.input-container {
  display: flex;
  padding: 10px;
  background-color: #fff;
  border-top: 1px solid #ccc;
}

input {
  flex: 1;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 5px;
}

button {
  padding: 10px 20px;
  margin-left: 10px;
  border: none;
  font-size: 1rem;
  font-weight: bold;
  border-radius: 10px;
  background-color: #007bff;
  color: #ffffff;
  cursor: pointer;
}

実行結果

サーバを立ち上げてブラウザで挙動を確認します。

npm run dev

会話履歴がチャット画面に収まり切れない状態でユーザが入力、もしくはBotからのレスポンスを表示すると、最新の入力値が表示される一番下の場所まで自動でスクロールされました。

chatscroll.gif

参考

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?