何をやるのか
LINEのように、チャット画面で新規メッセージを送信後、画面最下部まで自動でスクロールする機能の実装方法について解説します。
Reactで作ったSPAが前提の方法です。
1. チャット画面を実装
まずはサンプルとなるチャット画面を実装。
ChatScreen.jsx
import React, { useState } from 'react';
import './ChatScreen.css';
// ダミーデータを生成
const dummyMessages = Array(30)
.fill({})
.map((_, index) => ({
id: index,
content: `ダミーメッセージ #${index + 1}`,
}));
export const ChatScreen = () => {
const [messages, setMessages] = useState(dummyMessages);
const [inputValue, setInputValue] = useState('');
const onSendMessage = () => {
setMessages(messages.concat({ id: messages.length + 1, content: inputValue }));
setInputValue('');
};
return (
<div className='container'>
<ul className='messages-container'>
{messages.map((message, index) => (
<li key={message.id} className='message'>
<p>{message.content}</p>
</li>
))}
</ul>
<div className='input-container'>
<input
type='text'
className='input'
value={inputValue}
onChange={({ target }) => setInputValue(target.value)}
/>
<button className='button' onClick={onSendMessage}>
送信
</button>
</div>
</div>
);
};
ChatScreen.css
.container {
overflow-y: overlay;
display: inline-block;
}
.messages-container {
display: flex;
flex-direction: column;
padding: 50px;
margin-bottom: 100px;
}
.message {
padding: 5px 20px;
background-color: #dcf8c6;
display: block;
border-radius: 10px;
margin-top: 10px;
margin-bottom: 10px;
clear: both;
}
.input-container {
display: flex;
flex-direction: column;
justify-content: space-around;
height: 100px;
padding: 5px;
width: 100%;
position: fixed;
bottom: 30px;
}
.input {
width: 60%;
margin: auto;
border: 1px silver solid;
font-size: 15px;
line-height: 40px;
}
.button {
width: 100px;
margin: auto;
}
仕上がりはこんな感じです。
2. Reactのrefを使って末尾のメッセージを管理
(1) useRefでrefを定義
ChatScreen.tsx
const footRef = useRef(null)
React HookのuseRefでfootRefオブジェクトを定義。
デフォルト値はnull。
(2) 末尾のメッセージへrefを付与
ChatScreen.jsx
{messages.map((message, index) => (
<li key={message.id} className='message'>
<p>{message.content}</p>
{/* 以下の一行を追加 */}
{index === messages.length - 1 && <div ref={footRef}></div>}
</li>
))}
messages配列の末尾の要素、つまり最下部に表示されるメッセージへfootRefを付与。
3. useLayoutEffectで自動スクロール
ChatScreen.jsx
useLayoutEffect(() => {
footRef.current.scrollIntoView();
}, [messages]);
useLayoutEffect関数を記述し、第二引数の配列に渡された値(messages)の変更があるたびに,
"footRef.current.scrollIntoView()"で最下部の要素を画面上に表示。
最終的なコードは以下。
ChatScreen.jsx
import React, { useLayoutEffect, useRef, useState } from 'react';
import './ChatScreen.css';
// ダミーデータ
const dummyMessages = Array(30)
.fill({})
.map((_, index) => ({
id: index,
content: `ダミーメッセージ #${index + 1}`,
}));
export const ChatScreen = () => {
const [messages, setMessages] = useState(messagesData);
const [inputValue, setInputValue] = useState('');
const footRef = useRef(null);
const onSendMessage = () => {
setMessages(messages.concat({ id: messages.length + 1, content: inputValue }));
setInputValue('');
};
useLayoutEffect(() => {
footRef.current.scrollIntoView();
}, [messages]);
return (
<div className='container'>
<ul className='messages-container'>
{messages.map((message, index) => (
<li key={message.id} className='message'>
<p>{message.content}</p>
{/* 以下の一行を追加 */}
{index === messages.length - 1 && <div ref={footRef}></div>}
</li>
))}
</ul>
<div className='input-container'>
<input
type='text'
className='input'
value={inputValue}
onChange={({ target }) => setInputValue(target.value)}
/>
<button className='button' onClick={onSendMessage}>
送信
</button>
</div>
</div>
);
};
こんな感じで自動スクロールが実現できました。
今回はチャット画面を題材として扱いましたが、こちらのテクニックは割といろんな場面で応用できるのではないかなと思います。
こちらあくまで一方法となりますのでご参考までに。
↧