Reactを使用して、ボタンを押したときに指定のDOMの位置までスムーズにアニメーションしながらスクロールするサンプルを作成してみました
スムーズにスクロールさせる方法は色々あると思いますが、私は以下の方法を試してみました。
- 方法1
scrollTo()
を使う (※IEとSafarではうまく動かなかった) - 方法2
requestAnimationFrame
を使う
scrollTo()
scrollTo()
を呼び出すときにオプションでbehavior: 'smooth'
と指定することでスムーズにアニメーションしながらスクロースしてくれました。
※IEとSafariではスクロールするけど、アニメーションしないで、一瞬で対象の位置まで行ってしまいました
See the Pen react smooth scroll by 奥村健吾 (@okumurakengo) on CodePen.
const styles = {
width: '500px',
height: '500px',
};
function App() {
const div1 = React.useRef()
const div2 = React.useRef()
const div3 = React.useRef()
/**
* @param {HTMLElement} target
*/
function smoothScroll(target) {
const { top } = target.getBoundingClientRect()
window.scrollTo({
top: top + window.pageYOffset,
behavior: "smooth"
});
}
return (
<div>
<div ref={div1} style={{ background: 'yellowgreen', ...styles }}>
<p><button onClick={() => smoothScroll(div1.current)}>yellowgreen</button></p>
<p><button onClick={() => smoothScroll(div2.current)}>seagreen</button></p>
<p><button onClick={() => smoothScroll(div3.current)}>skyblue</button></p>
</div>
<div ref={div2} style={{ background: 'seagreen', ...styles }}>
<p><button onClick={() => smoothScroll(div1.current)}>yellowgreen</button></p>
<p><button onClick={() => smoothScroll(div2.current)}>seagreen</button></p>
<p><button onClick={() => smoothScroll(div3.current)}>skyblue</button></p>
</div>
<div ref={div3} style={{ background: 'skyblue', ...styles }}>
<p><button onClick={() => smoothScroll(div1.current)}>yellowgreen</button></p>
<p><button onClick={() => smoothScroll(div2.current)}>seagreen</button></p>
<p><button onClick={() => smoothScroll(div3.current)}>skyblue</button></p>
</div>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
requestAnimationFrame
スクロール位置の判定は自分でしないといけないですが、以下のようにするとスムーズにアニメーションしながらスクロースしてくれました、
私の環境ではIE、Safariでも動作するのを確認できました。
See the Pen react smooth scroll (requestAnimationFrame) by 奥村健吾 (@okumurakengo) on CodePen.
/**
* 下にスクロールするピクセル値の間隔
* @constant
* @type {number}
*/
const INTERVAL = 15;
const styles = {
width: '500px',
height: '500px',
};
function App() {
const requestRef = React.useRef()
const targetTopRef = React.useRef()
const startYOffset = React.useRef()
const div1 = React.useRef()
const div2 = React.useRef()
const div3 = React.useRef()
/**
* @param {HTMLElement} target
* @returns {void}
*/
function smoothScroll(target) {
// スクロール対象の位置を取得
({ top: targetTopRef.current } = target.getBoundingClientRect())
// スクロール開始位置を取得
startYOffset.current = window.pageYOffset
// アニメーション開始
animate()
}
/**
* @returns {void}
*/
function animate() {
requestRef.current = requestAnimationFrame(animate);
if (targetTopRef.current > 0) {
// 下にスクロールする場合
if (((startYOffset.current + targetTopRef.current) - window.pageYOffset) < INTERVAL) {
// 残りのスクロールするピクセル値が、INTERVALの値より少ない場合は
// 行き過ぎないようにスクロール値を調整
window.scrollTo(0, window.pageYOffset + ((startYOffset.current + targetTopRef.current) - window.pageYOffset))
} else {
// 下にスクロール
window.scrollTo(0, window.pageYOffset + INTERVAL)
}
if (((startYOffset.current + targetTopRef.current) <= window.pageYOffset)
|| (document.documentElement.scrollTop + window.innerHeight) >= (document.documentElement.offsetHeight)) {
// スクロールの対象まできたか、ウィンドウの最下部にきたらスクロール終了
cancelAnimationFrame(requestRef.current);
}
} else {
// 上にスクロールする場合
if (-((startYOffset.current + targetTopRef.current) - window.pageYOffset) < INTERVAL) {
// 残りのスクロールするピクセル値が、INTERVALの値より少ない場合は
// 行き過ぎないようにスクロール値を調整
window.scrollTo(0, window.pageYOffset + ((startYOffset.current + targetTopRef.current) - window.pageYOffset))
} else {
// 上にスクロール
window.scrollTo(0, window.pageYOffset - INTERVAL)
}
if ((startYOffset.current + targetTopRef.current) >= window.pageYOffset) {
// スクロールの対象まできたらスクロール終了
cancelAnimationFrame(requestRef.current);
}
}
}
return (
<div>
<div ref={div1} style={{ background: 'yellowgreen', ...styles }}>
<p><button onClick={() => smoothScroll(div1.current)}>yellowgreen</button></p>
<p><button onClick={() => smoothScroll(div2.current)}>seagreen</button></p>
<p><button onClick={() => smoothScroll(div3.current)}>skyblue</button></p>
</div>
<div ref={div2} style={{ background: 'seagreen', ...styles }}>
<p><button onClick={() => smoothScroll(div1.current)}>yellowgreen</button></p>
<p><button onClick={() => smoothScroll(div2.current)}>seagreen</button></p>
<p><button onClick={() => smoothScroll(div3.current)}>skyblue</button></p>
</div>
<div ref={div3} style={{ background: 'skyblue', ...styles }}>
<p><button onClick={() => smoothScroll(div1.current)}>yellowgreen</button></p>
<p><button onClick={() => smoothScroll(div2.current)}>seagreen</button></p>
<p><button onClick={() => smoothScroll(div3.current)}>skyblue</button></p>
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));