Last active
June 29, 2022 12:45
-
-
Save kobitoDevelopment/240f5a24cad236021776967bee4bf775 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <div class="js-scroll-area"> | |
| <div class="js-scroll-slider" data-scroll-area=".js-scroll-area" data-auto-speed-ratio="0.5" data-scroll-ratio="0.06"> | |
| <div class="js-scroll-slider__item"><img src="https://picsum.photos/id/275/400/400" alt=""></div> | |
| <div class="js-scroll-slider__item"><img src="https://picsum.photos/id/200/400/400" alt=""></div> | |
| <div class="js-scroll-slider__item"><img src="https://picsum.photos/id/1062/400/400" alt=""></div> | |
| <div class="js-scroll-slider__item"><img src="https://picsum.photos/id/1003/400/400" alt=""></div> | |
| <div class="js-scroll-slider__item"><img src="https://picsum.photos/id/1001/400/400" alt=""></div> | |
| </div> | |
| <div class="js-scroll-slider is-reverse" data-scroll-area=".js-scroll-area" data-auto-speed-ratio="0.5" data-scroll-ratio="0.06"> | |
| <div class="js-scroll-slider__item"><img src="https://picsum.photos/id/270/400/400" alt=""></div> | |
| <div class="js-scroll-slider__item"><img src="https://picsum.photos/id/10/400/400" alt=""></div> | |
| <div class="js-scroll-slider__item"><img src="https://picsum.photos/id/103/400/400" alt=""></div> | |
| </div> | |
| </div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //要素数が変わっても一定の速度を保つ等速スクロールスライダー縦(ドラッグ動作、スクロール連動対応) | |
| const scrollSlider = document.querySelectorAll(".js-scroll-slider"); | |
| if (scrollSlider.length > 0) { | |
| scrollSlider.forEach(function (slider) { | |
| //スクロールスピード設定 | |
| const speed = slider.dataset.autoSpeedRatio ? Number(slider.dataset.autoSpeedRatio) : 0.5; | |
| //スライド要素を取得 | |
| const children = slider.children; | |
| const childLength = children.length; | |
| //スライド要素一式を文字列で取得(複製用) | |
| let baseChildren = ""; | |
| for (let i = 0; i < children.length; i++) { | |
| baseChildren += children[i].outerHTML; | |
| } | |
| //スライド要素の横幅を取得 | |
| const firstChild = slider.firstElementChild; | |
| //スライダーの表示エリア | |
| const area = document.querySelector(slider.getAttribute("data-scroll-area")); | |
| //スライダー定義関数 | |
| let sliderHeight, areaHeight; | |
| let countHeight = 0; | |
| let addCount = 1; | |
| const initializeSlider = function (countHeight, addCount) { | |
| //表示領域の高さを取得 | |
| areaHeight = area.clientHeight; | |
| //スライダー全体の高さを取得 | |
| const styles = getComputedStyle(firstChild); | |
| const height = parseFloat(styles.height); | |
| const marginBottom = parseFloat(styles.marginBottom); | |
| sliderHeight = (height + marginBottom) * childLength; | |
| //画面外まで表示を確保するため要素を複製 | |
| const checkHeight = areaHeight * 3; | |
| countHeight = sliderHeight * addCount; | |
| while (countHeight < checkHeight) { | |
| slider.insertAdjacentHTML("beforeend", baseChildren); | |
| ++addCount; | |
| countHeight = sliderHeight * addCount; | |
| } | |
| //スライダーの初期値設定 | |
| slider.style.marginTop = "-" + sliderHeight + "px"; | |
| }; | |
| initializeSlider(countHeight, addCount); | |
| /* | |
| アニメーション設定 | |
| */ | |
| let requestID; | |
| let y = 0; | |
| //ウィンドウの高さ取得 | |
| let winHeight = window.innerHeight; | |
| window.addEventListener("resize", function () { | |
| winHeight = window.innerHeight; | |
| }); | |
| //イージング(線形補完) | |
| const lerp = function (x, y, p) { | |
| return x + (y - x) * p; | |
| }; | |
| //スクロール連動用変数 | |
| let startScrollY = 0; | |
| let easingScrollY = 0; | |
| let diffScrollY = 0; | |
| const baseAmount = 1; | |
| //反転処理判別 | |
| const isReverse = slider.classList.contains("is-reverse"); | |
| //スクロールイベント関数 | |
| const scrollRatio = slider.dataset.scrollRatio ? Number(slider.dataset.scrollRatio) : 0.05; | |
| //表示エリアの位置 | |
| let areaPosi = area.getBoundingClientRect().top; | |
| //スクロール関数 | |
| const scrollAnime = function () { | |
| //スクロール開始位置からのスクロール量 | |
| startScrollY = winHeight - areaPosi; | |
| //イージング(線形補完)を適用したスクロール量。startScrollYから少し遅れて追従する | |
| easingScrollY = lerp(easingScrollY, startScrollY, scrollRatio); | |
| //startScrollYとeasingScrollYの差。スクロールが止まれば、いずれゼロに近づく | |
| diffScrollY = startScrollY - easingScrollY; | |
| //スクロールによって増減する増加量 | |
| const scrollAmount = Math.abs(diffScrollY) / 10; | |
| const getY = (baseAmount + scrollAmount) * speed; | |
| //値を設定 | |
| if (isReverse) { | |
| y += getY; | |
| } else { | |
| y -= getY; | |
| } | |
| //スライダーの終了ポイントを過ぎていたら位置を戻す | |
| if ((!isReverse && y <= -sliderHeight) || (isReverse && y >= sliderHeight)) { | |
| y = 0; | |
| } | |
| slider.style.transform = "translateY(" + y + "px)"; | |
| //画面内の時だけ再帰処理 | |
| if (isIntersecting) { | |
| requestID = requestAnimationFrame(scrollAnime); | |
| } | |
| }; | |
| /* | |
| ドラッグ対応 | |
| */ | |
| //スライダーのstyleを取得 | |
| const sliderStyles = getComputedStyle(slider); | |
| //クリック、タッチされているか判別 | |
| let isDown = false; | |
| //ドラッグで下に動かしているか判別 | |
| let isBottomMove = false; | |
| // クリック開始位置を保存 | |
| let startY; | |
| //スライダー位置を保存 | |
| let sliderY; | |
| //画面内に存在しているか判別 | |
| let isIntersecting = false; | |
| //マウスダウン、タッチスタート(移動開始) | |
| const startFunc = function (e) { | |
| e.preventDefault(); | |
| slider.style.transition = ""; | |
| //画面上のy位置を取得 | |
| startY = e.pageY; | |
| //スライダーにclass追加 | |
| slider.classList.add("is-drag"); | |
| isDown = true; | |
| //マウスポインタを変更 | |
| slider.style.cursor = "grabbing"; | |
| //自動スクロール停止 | |
| cancelAnimationFrame(requestID); | |
| //スライダー位置取得 | |
| sliderY = parseFloat(new DOMMatrix(sliderStyles.transform).m42); | |
| }; | |
| //マウスムーブ、タッチムーブ(移動中) | |
| const moveFunc = function (e) { | |
| //マウスダウン、タッチスタート時のみ処理 | |
| if (!isDown) { | |
| return; | |
| } | |
| e.preventDefault(); | |
| //移動距離を取得 | |
| let moveY = startY - e.pageY; | |
| //上下どちらに移動しているか判別 | |
| if (moveY < 0) { | |
| isBottomMove = true; | |
| } else { | |
| isBottomMove = false; | |
| } | |
| //スライダーを移動させる | |
| requestID = requestAnimationFrame(function () { | |
| y = sliderY - moveY; | |
| slider.style.transform = "translateY(" + y + "px)"; | |
| }); | |
| }; | |
| //マウスアップ、タッチエンド(移動終了) | |
| const endFunc = function (e) { | |
| e.preventDefault(); | |
| //設定をリセット | |
| slider.classList.remove("is-drag"); | |
| isDown = false; | |
| //マウスポインタを変更 | |
| slider.style.cursor = "pointer"; | |
| //スライダーの終了ポイントを過ぎていたら位置を戻す | |
| if (!isBottomMove && y * -1 >= sliderHeight) { | |
| y = sliderHeight - y * -1; | |
| slider.style.transform = "translateY(" + y + "px)"; | |
| } else if (isBottomMove && y >= 0) { | |
| y = -(sliderHeight - y); | |
| slider.style.transform = "translateY(" + y + "px)"; | |
| } | |
| //アニメーションをリセットし画面内の場合は自動スクロール開始 | |
| cancelAnimationFrame(requestID); | |
| if (isIntersecting) { | |
| requestID = requestAnimationFrame(scrollAnime); | |
| } | |
| }; | |
| //マウスドラッグ対応 | |
| slider.addEventListener("mouseenter", function () { | |
| //マウスポインタを変更 | |
| slider.style.cursor = "pointer"; | |
| }); | |
| slider.addEventListener("mousedown", startFunc); | |
| slider.addEventListener("mousemove", moveFunc); | |
| slider.addEventListener("mouseup", endFunc); | |
| slider.addEventListener("mouseleave", endFunc); | |
| //スクロール処理 | |
| const listener = { | |
| handleEvent: function () { | |
| //表示エリアの位置を更新(レイアウトシフトを起こすので、変更が起きたときのみrequestAnimationFrame外で処理する) | |
| areaPosi = area.getBoundingClientRect().top; | |
| }, | |
| }; | |
| /* | |
| 要素が画面内に入ったら処理開始 | |
| */ | |
| const observer = new IntersectionObserver(function (entries) { | |
| entries.forEach(function (entry) { | |
| if (entry.isIntersecting) { | |
| cancelAnimationFrame(requestID); | |
| //自動スクロール開始 | |
| requestID = requestAnimationFrame(scrollAnime); | |
| //画面に表示されている要素のみスクロールイベントへ追加 | |
| window.addEventListener("scroll", listener, { passive: true }); | |
| //画面内判定フラグ | |
| isIntersecting = true; | |
| } else { | |
| //自動スクロール解除 | |
| cancelAnimationFrame(requestID); | |
| //画面外のときはスクロールイベント削除 | |
| window.removeEventListener("scroll", listener, { passive: true }); | |
| //画面内判定フラグ | |
| isIntersecting = false; | |
| } | |
| }); | |
| }); | |
| //observer監視開始 | |
| observer.observe(area); | |
| /* | |
| レスポンシブ対応 | |
| */ | |
| //表示エリアリサイズ監視 ResizeObserver | |
| const resizeObserver = new ResizeObserver(function () { | |
| //スライダーが途切れないかチェック(画面外の要素が足りなければ追加する) | |
| initializeSlider(countHeight, addCount); | |
| //スライダー再定義 | |
| cancelAnimationFrame(requestID); | |
| requestID = requestAnimationFrame(scrollAnime); | |
| }); | |
| //リサイズ監視開始(スライダー本体ではなく、最初のスライダー内要素が可変すると発火) | |
| resizeObserver.observe(firstChild); | |
| }); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| .js-scroll-area { | |
| position: relative; | |
| overflow: hidden; | |
| height: 100vh; | |
| .js-scroll-slider { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 45%; | |
| &.is-scroll { | |
| transition: transform 0.05s; | |
| } | |
| .js-scroll-slider__item { | |
| margin-bottom: 20px; | |
| } | |
| & + .js-scroll-slider { | |
| left: auto; | |
| right: 0; | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment