コード

無限ループスライダー

<!DOCTYPE html>

<html lang=”ja”>

<head>

<meta charset=”UTF-8″>

<meta name=”viewport” content=”width=device-width, initial-scale=1.0″>

<title>無限ループスライダー</title>

<!– Tailwind CSSを読み込み –>

<script src=”https://cdn.tailwindcss.com”></script>

<style>

/* Interフォントを読み込み(Tailwindで指定された場合) */

body {

font-family: “Inter”, sans-serif;

}

/* スライダーコンテナの基本スタイル */

.slider-container {

overflow: hidden;

width: 100%;

position: relative; /* スライダーコンテンツのアニメーションのために必要 */

/* 高さはこの中のコンテンツによって決まる */

}

/* スライダーコンテンツラッパー – スライダーコンテンツを保持 */

.slider-content-wrapper {

width: 100%;

height: auto; /* コンテンツの高さに合わせる */

}

/* スライダーコンテンツ(画像を流す部分) */

.slider-content {

display: flex;

white-space: nowrap; /* 画像が改行されないように */

height: auto; /* 子要素(画像)の高さに合わせる */

}

/* 各スライドアイテム */

.slider-item {

flex-shrink: 0; /* 幅が縮まないように */

height: auto; /* 画像のアスペクト比を維持しながら高さを自動調整 */

object-fit: cover; /* 画像をはみ出さないようにトリミング */

display: block;

cursor: pointer;

}

/* ライトボックスのスタイルは変更なし */

.lightbox-overlay {

position: fixed;

top: 0;

left: 0;

width: 100%;

height: 100%;

background-color: rgba(0, 0, 0, 0.9);

display: flex;

justify-content: center;

align-items: center;

z-index: 1000;

opacity: 0;

visibility: hidden;

transition: opacity 0.3s ease, visibility 0.3s ease;

}

.lightbox-overlay.active {

opacity: 1;

visibility: visible;

}

.lightbox-content {

position: relative;

max-width: 90%;

max-height: 90%;

overflow: hidden;

border-radius: 0.75rem; /* rounded-lg */

box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); /* shadow-xl */

}

.lightbox-image {

width: auto; /* 画像の元の比率を維持 */

height: auto;

max-width: 100%;

max-height: 100%;

display: block;

border-radius: 0.75rem; /* rounded-lg */

}

.lightbox-close {

position: absolute;

top: 1rem; /* 4px */

right: 1rem; /* 4px */

background-color: rgba(255, 255, 255, 0.8);

color: #000;

border: none;

border-radius: 9999px; /* full rounded */

width: 2.5rem; /* w-10 */

height: 2.5rem; /* h-10 */

font-size: 1.25rem; /* text-xl */

display: flex;

justify-content: center;

align-items: center;

cursor: pointer;

transition: background-color 0.2s ease, color 0.2s ease;

z-index: 1001; /* 画像より手前に */

font-weight: bold;

}

.lightbox-close:hover {

background-color: #fff;

color: #ef4444; /* red-500 */

}

</style>

</head>

<body>

<!– 上のスライダー –>

<div class=”slider-wrapper”>

<div class=”slider-container”>

<div id=”slider-content-top” class=”slider-content”>

<!– JavaScriptで画像がここに追加されます –>

</div>

</div>

</div>

<!– 下のスライダー (上のスライダーとの間にmt-4で空白を空ける) –>

<div class=”slider-wrapper mt-4″>

<div class=”slider-container”>

<div id=”slider-content-bottom” class=”slider-content”>

<!– JavaScriptで画像がここに追加されます –>

</div>

</div>

</div>

<!– ライトボックス (共通) –>

<div id=”lightbox-overlay” class=”lightbox-overlay”>

<div class=”lightbox-content”>

<button id=”lightbox-close” class=”lightbox-close”>&times;</button>

<img id=”lightbox-image” class=”lightbox-image” src=”” alt=”拡大画像”>

</div>

</div>

<script>

document.addEventListener(‘DOMContentLoaded’, () => {

const lightboxOverlay = document.getElementById(‘lightbox-overlay’);

const lightboxImage = document.getElementById(‘lightbox-image’);

const lightboxClose = document.getElementById(‘lightbox-close’);

// — スライダー設定 —

// 上のスライダー用の画像

const imagesTop = [

‘https://placehold.co/1200×675/FFDDAA/000?text=Top+Image+1’,

‘https://placehold.co/1200×675/FFCC99/000?text=Top+Image+2’,

‘https://placehold.co/1200×675/FFBB88/000?text=Top+Image+3’,

‘https://placehold.co/1200×675/AAEEBB/000?text=Top+Image+4’,

‘https://placehold.co/1200×675/CCFFDD/000?text=Top+Image+5’,

‘https://placehold.co/1200×675/EEFFCC/000?text=Top+Image+6’,

‘https://placehold.co/1200×675/FFEEAA/000?text=Top+Image+7’,

‘https://placehold.co/1200×675/A0C4FF/000?text=Top+Image+8’,

‘https://placehold.co/1200×675/BBDDFF/000?text=Top+Image+9’,

‘https://placehold.co/1200×675/DDEEFF/000?text=Top+Image+10’

];

// 下のスライダー用の画像

const imagesBottom = [

‘https://placehold.co/1200×675/BBDDFF/000?text=Bottom+Image+1’,

‘https://placehold.co/1200×675/DDEEFF/000?text=Bottom+Image+2’,

‘https://placehold.co/1200×675/A0C4FF/000?text=Bottom+Image+3’,

‘https://placehold.co/1200×675/EEFFCC/000?text=Bottom+Image+4’,

‘https://placehold.co/1200×675/FFEEAA/000?text=Bottom+Image+5’,

‘https://placehold.co/1200×675/CCFFDD/000?text=Bottom+Image+6’,

‘https://placehold.co/1200×675/AAEEBB/000?text=Bottom+Image+7’,

‘https://placehold.co/1200×675/FFBB88/000?text=Bottom+Image+8’,

‘https://placehold.co/1200×675/FFCC99/000?text=Bottom+Image+9’,

‘https://placehold.co/1200×675/FFDDAA/000?text=Bottom+Image+10’

];

// スライダーの速度 (ピクセル/フレーム、数値が大きいほど速い)

const SLIDER_SPEED = 0.5;

// スライダーのロジックをカプセル化するクラス

class ImageSlider {

constructor(sliderContentId, direction, speed, imagesArray) {

this.sliderContent = document.getElementById(sliderContentId);

this.direction = direction; // 1: 右から左, -1: 左から右

this.speed = speed;

this.images = imagesArray;

this.animationId = null;

this.currentPosition = 0;

this.sliderWidth = 0;

this.imageWidth = 0;

this.initializeSliderImages();

this.addEventListeners();

}

/**

* スライダーに画像を生成して追加し、幅を調整します。

* 無限ループのために画像を複製します。

*/

initializeSliderImages() {

this.sliderContent.innerHTML = ”; // 既存の画像をクリア

const containerWidth = this.sliderContent.offsetWidth;

if (containerWidth === 0) {

requestAnimationFrame(() => this.initializeSliderImages());

return;

}

// 表示する画像の枚数を画面幅によって切り替え

const imagesToShow = window.innerWidth < 640 ? 1 : 3;

// 常に指定枚数の画像が表示されるように、1枚あたりの幅を計算

this.imageWidth = containerWidth / imagesToShow;

// スライダーにオリジナル画像とその複製を追加

const totalImagesToAppend = this.images.length * 2;

for (let i = 0; i < totalImagesToAppend; i++) {

const src = this.images[i % this.images.length];

const img = document.createElement(‘img’);

img.src = src;

img.classList.add(‘slider-item’);

img.alt = `スライダー画像 ${i + 1}`;

img.style.width = `${this.imageWidth}px`;

img.onerror = () => {

console.error(‘Failed to load image:’, src);

const fallbackHeight = this.imageWidth * (9 / 16);

img.src = `https://placehold.co/${Math.round(this.imageWidth)}x${Math.round(fallbackHeight)}/CCCCCC/000?text=Error`;

};

this.sliderContent.appendChild(img);

}

// スライダー全体の幅 (オリジナル画像のセットの幅) を計算

this.sliderWidth = this.imageWidth * this.images.length;

// スライダーの初期位置を設定

if (this.direction === 1) { // 右から左

this.currentPosition = 0;

} else { // 左から右

this.currentPosition = -this.sliderWidth;

}

this.startAnimation();

}

/**

* スライダーのアニメーションフレームを更新します。

*/

animateSlider() {

if (this.direction === 1) { // 右から左

this.currentPosition -= this.speed;

if (this.currentPosition <= -this.sliderWidth) {

this.currentPosition = 0;

}

} else { // 左から右

this.currentPosition += this.speed;

if (this.currentPosition >= 0) {

this.currentPosition = -this.sliderWidth;

}

}

this.sliderContent.style.transform = `translateX(${this.currentPosition}px)`;

this.animationId = requestAnimationFrame(() => this.animateSlider());

}

/**

* スライダーアニメーションを開始します。

*/

startAnimation() {

if (this.animationId) {

cancelAnimationFrame(this.animationId);

}

this.animationId = requestAnimationFrame(() => this.animateSlider());

}

/**

* スライダーアニメーションを停止します。

*/

stopAnimation() {

if (this.animationId) {

cancelAnimationFrame(this.animationId);

}

}

/**

* イベントリスナーを設定します。

*/

addEventListeners() {

this.sliderContent.addEventListener(‘click’, (event) => {

if (event.target.classList.contains(‘slider-item’)) {

const imgSrc = event.target.src;

lightboxImage.src = imgSrc;

lightboxOverlay.classList.add(‘active’);

}

});

}

}

// — スライダーのインスタンス化 —

// 上のスライダーにimagesTopを、下のスライダーにimagesBottomを使用

const topSlider = new ImageSlider(‘slider-content-top’, 1, SLIDER_SPEED, imagesTop); // 右から左

const bottomSlider = new ImageSlider(‘slider-content-bottom’, -1, SLIDER_SPEED, imagesBottom); // 左から右

// — ライトボックスのイベントリスナー (共通) —

lightboxClose.addEventListener(‘click’, () => {

lightboxOverlay.classList.remove(‘active’);

});

document.addEventListener(‘keydown’, (event) => {

if (event.key === ‘Escape’ && lightboxOverlay.classList.contains(‘active’)) {

lightboxOverlay.classList.remove(‘active’);

}

});

// ウィンドウサイズ変更時の処理

window.addEventListener(‘resize’, () => {

// 両方のスライダーを停止し、再初期化して、新しいサイズに対応

topSlider.stopAnimation();

bottomSlider.stopAnimation();

topSlider.initializeSliderImages();

bottomSlider.initializeSliderImages();

});

});

</script>

</body>

</html>

コメント

タイトルとURLをコピーしました