Skip to content

Instantly share code, notes, and snippets.

@kobitoDevelopment
Last active June 29, 2022 12:53
Show Gist options
  • Select an option

  • Save kobitoDevelopment/7fbaa1d0d508b8e53ae12997bb38e2e2 to your computer and use it in GitHub Desktop.

Select an option

Save kobitoDevelopment/7fbaa1d0d508b8e53ae12997bb38e2e2 to your computer and use it in GitHub Desktop.
<div class="wrapper">
<div class="js-scroll-slider-wrap">
<div class="js-scroll-slider" data-auto-speed-ratio="0.5">
<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>
<div class="js-scroll-slider-wrap">
<div class="js-scroll-slider is-reverse" data-auto-speed-ratio="0.5">
<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>
</div>
//要素数が変わっても一定の速度を保つ等速スクロールスライダー(ドラッグ、スワイプ動作対応)
const scrollSlider = document.querySelectorAll(".js-scroll-slider");
if (scrollSlider.length > 0) {
scrollSlider.forEach(function (slider, index) {
//スクロールスピード設定
const speed = slider.dataset.autoSpeedRatio ? Number(slider.dataset.autoSpeedRatio) : 0.5;
//observer監視用 親要素生成
const sliderWrap = document.querySelector(".js-scroll-slider-wrap");
//スライド要素を取得
const children = slider.children; // スライダー要素1つずつを子要素全てを含んで配列に格納
const childLength = children.length;
//スライド要素一式を文字列で取得(複製用)
let baseChildren = "";
for (let i = 0; i < children.length; i++) {
baseChildren += children[i].outerHTML;
}
//スライド要素を1つ取得
const firstChild = slider.firstElementChild;
//スライダー定義関数
let sliderWidth, winWidth;
let countWidth = 0;
let addCount = 1;
const initializeSlider = function (countWidth, addCount) {
//スライダー全体の幅を取得
const styles = getComputedStyle(firstChild);
const width = parseFloat(styles.width);
const marginRight = parseFloat(styles.marginRight);
sliderWidth = (width + marginRight) * childLength;
//画面外まで表示を確保するため要素を複製
winWidth = window.innerWidth;
const checkWidth = winWidth * 3;
countWidth = sliderWidth * addCount;
while (countWidth < checkWidth) {
slider.insertAdjacentHTML("beforeend", baseChildren);
++addCount;
countWidth = sliderWidth * addCount;
}
//スライダー位置(複製したスライダーを画面外の左右に配置)
slider.style.marginLeft = "-" + sliderWidth + "px";
};
initializeSlider(countWidth, addCount);
/*
アニメーション設定
*/
let requestID;
let x = 0;
//反転処理判別
const isReverse = slider.classList.contains("is-reverse");
//スクロール関数
const scrollAnime = function () {
//スライダーの終了ポイントを過ぎていたら位置を戻す
if ((!isReverse && x <= -sliderWidth) || (isReverse && x >= sliderWidth)) {
x = 0;
}
slider.style.transform = "translateX(" + x + "px)";
if (isReverse) {
x += 1 * speed;
} else {
x -= 1 * speed;
}
requestID = requestAnimationFrame(scrollAnime);
};
/*
要素が画面内に入ったら処理開始
*/
//画面内に存在しているか判別
let isIntersecting = false;
const observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
cancelAnimationFrame(requestID);
//自動スクロール開始
requestID = requestAnimationFrame(scrollAnime);
//画面内判定フラグ(endFunc用)
isIntersecting = true;
} else {
//自動スクロール解除
cancelAnimationFrame(requestID);
//画面内判定フラグ(endFunc用)
isIntersecting = false;
}
});
});
//observer監視開始
observer.observe(sliderWrap);
/*
スワイプ、ドラッグ対応
*/
//スライダーのstyleを取得
const sliderStyles = getComputedStyle(slider);
//クリック、タッチされているか判別
let isDown = false;
//スワイプ、ドラッグで右に動かしているか判別
let isRightMove = false;
// クリック開始位置を保存
let startX;
//スライダー位置を保存
let sliderX;
//マウスダウン、タッチスタート(移動開始)
const startFunc = function (e) {
e.preventDefault();
//画面上のx位置を取得
if (e.type === "touchstart") {
startX = e.changedTouches[0].pageX;
} else {
startX = e.pageX;
}
//スライダーにclass追加
slider.classList.add("is-drag");
isDown = true;
//マウスポインタを変更
slider.style.cursor = "grabbing";
//自動スクロール停止
cancelAnimationFrame(requestID);
//スライダー位置取得 (translateXの値のみ取得)
let matrix = new DOMMatrix(sliderStyles.transform);
sliderX = parseFloat(matrix.m41);
};
//マウスムーブ、タッチムーブ(移動中)
const moveFunc = function (e) {
//マウスダウン、タッチスタート時のみ処理
if (!isDown) {
return;
}
e.preventDefault();
//移動距離を取得
let moveX;
if (e.type === "touchmove") {
moveX = startX - e.changedTouches[0].pageX;
} else {
moveX = startX - e.pageX;
}
//左右どちらに移動しているか判別
if (moveX < 0) {
isRightMove = true;
} else {
isRightMove = false;
}
//スライダーを移動させる
requestID = requestAnimationFrame(function () {
x = sliderX - moveX;
slider.style.transform = "translateX(" + x + "px)";
});
};
//マウスアップ、タッチエンド(移動終了)
const endFunc = function (e) {
e.preventDefault();
//設定をリセット
slider.classList.remove("is-drag");
isDown = false;
//マウスポインタを変更
slider.style.cursor = "pointer";
//スライダーの終了ポイントを過ぎていたら位置を戻す
if (!isRightMove && x * -1 >= sliderWidth) {
x = sliderWidth - x * -1;
slider.style.transform = "translateX(" + x + "px)";
} else if (isRightMove && x >= 0) {
x = -(sliderWidth - x);
slider.style.transform = "translateX(" + x + "px)";
}
//アニメーションをリセットし画面内の場合は自動スクロール開始
cancelAnimationFrame(requestID);
if (isIntersecting) {
requestID = requestAnimationFrame(scrollAnime);
}
};
//スワイプ対応
slider.addEventListener("touchstart", startFunc, { passive: true });
slider.addEventListener("touchmove", moveFunc, { passive: true });
slider.addEventListener("touchend", endFunc);
//マウスドラッグ対応
slider.addEventListener("mouseenter", function () {
//マウスポインタを変更
slider.style.cursor = "pointer";
});
slider.addEventListener("mousedown", startFunc);
slider.addEventListener("mousemove", moveFunc);
slider.addEventListener("mouseup", endFunc);
slider.addEventListener("mouseleave", endFunc);
/*
レスポンシブ対応
*/
window.addEventListener("resize", function () {
// スライダー要素幅・window幅を再計算
initializeSlider(countWidth, addCount);
//スライダー再定義
cancelAnimationFrame(requestID);
requestID = requestAnimationFrame(scrollAnime);
});
});
}
.wrapper {
overflow: hidden;
}
.js-scroll-slider {
display: flex;
margin-bottom: 20px;
}
.js-scroll-slider__item {
flex-shrink: 0;
width: 400px; //希望のスライダー横幅
aspect-ratio: 1/1; //希望のスライダー縦横比
margin-right: 20px;
img {
width: 100%;
height: 100%;
}
@include mqS {
width: 200px;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment