1. デザインのタイプ
【ダイナミック・カードスライダー(タイムライン同期型)】
背景画像とプレビューカードが連動し、滑らかなトランジションで切り替わる、没入感の高いモダンなスライダーデザイン。中央の軸から左右へ交互に展開されるアニメーションが、情報の「流れ」をドラマチックに演出します。
2. デザインの特徴
- フルスクリーン・イマージョン
コンテナいっぱいに広がる背景画像で視覚的な没入感を最大化。 - シームレス・トランジション
GSAPを使用した高度なアニメーションにより、情報の切り替えを劇的に演出します。 - インタラクティブ・フィードバック
操作ボタンにホバー・アクティブ時のリアクションを実装し、直感的な操作感を実現。
3. 紹介コード
<style>
/* --- 余白リセット --- */
#slider-container, #slider-container * {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#slider-container {
margin: 2em auto;
background-color: #000;
color: #FFFFFFDD;
position: relative;
overflow: hidden;
font-family: "Inter", sans-serif;
width: 100%;
max-width: 900px;
height: 400px;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}
#slider-container .card {
position: absolute;
left: 0;
top: 0 !important;
width: 100%;
height: 400px;
background-position: center;
background-size: cover;
z-index: 10;
}
#slider-container .card-content {
position: absolute;
z-index: 40;
color: #FFFFFFDD;
padding-left: 8px;
padding-right: 4px;
width: 100%;
pointer-events: none;
}
#slider-container .content-place { margin-top: 2px; font-size: 0.75rem; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#slider-container .content-title-1, #slider-container .content-title-2 { font-weight: 600; font-size: 0.95rem; font-family: "Oswald", sans-serif; line-height: 1.1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#slider-container .content-start { width: 20px; height: 2px; border-radius: 99px; background-color: #FFFFFFDD; margin-top: 4px; }
#slider-container .details {
z-index: 80 !important;
position: absolute;
top: 60px;
left: 40px;
max-width: 350px;
pointer-events: none;
opacity: 0;
visibility: hidden;
}
#slider-container .details > .cta { pointer-events: auto; }
#slider-container .details .title-1, #slider-container .details .title-2 { font-weight: 600; font-size: 36px; font-family: "Oswald", sans-serif; line-height: 1.1; }
#slider-container .details .place-box { height: 30px; overflow: hidden; }
#slider-container .details .place-box .text { padding-top: 10px; font-size: 14px; position: relative; }
#slider-container .details .place-box .text:before { top: 0; left: 0; position: absolute; content: ""; width: 20px; height: 3px; border-radius: 99px; background-color: white; }
#slider-container .details .title-box-1, #slider-container .details .title-box-2 { margin-top: 2px; height: 45px; overflow: hidden; }
#slider-container .details > .desc { margin-top: 10px; font-size: 13px; line-height: 1.5; color: #ccc; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
#slider-container .details > .cta { margin-top: 15px; display: flex; align-items: center; }
#slider-container .details > .cta > .bookmark { border: none; background-color: #ecad29; width: 30px; height: 30px; border-radius: 99px; color: white; display: grid; place-items: center; cursor: pointer; transition: transform 0.2s; }
#slider-container .details > .cta > .bookmark:active { transform: scale(0.9); }
#slider-container .details > .cta > .discover { border: 1px solid #ffffff; background-color: transparent; height: 30px; border-radius: 99px; color: #ffffff; padding: 0 16px; font-size: 11px; margin-left: 10px; text-transform: uppercase; cursor: pointer; transition: background 0.2s; }
#slider-container .details > .cta > .discover:hover { background: #ffffff22; }
#slider-container .pagination {
position: absolute;
z-index: 100;
bottom: 20px;
left: 40px;
display: flex;
align-items: center;
gap: 15px;
}
#slider-container .pagination > .arrow {
width: 36px;
height: 36px;
border-radius: 999px;
border: 1px solid #ffffff55;
display: grid;
place-items: center;
cursor: pointer;
pointer-events: auto;
transition: all 0.2s ease;
background-color: transparent;
}
#slider-container .pagination > .arrow:hover { background-color: rgba(255, 255, 255, 0.2); border-color: #ffffff; }
#slider-container .pagination > .arrow:active { background-color: rgba(255, 255, 255, 0.4); transform: scale(0.9); }
#slider-container .pagination > .arrow svg { width: 18px; height: 18px; color: #ffffff; transition: color 0.2s; }
#slider-container .pagination .progress-sub-container { width: 120px; height: 36px; display: flex; align-items: center; }
#slider-container .pagination .progress-sub-background { width: 100%; height: 2px; background-color: #ffffff33; }
#slider-container .pagination .progress-sub-foreground { height: 2px; background-color: #ecad29; width: 0%; }
#slider-container .slide-numbers { width: 30px; height: 36px; overflow: hidden; position: relative; }
#slider-container .slide-numbers .item { width: 30px; height: 36px; position: absolute; color: white; display: grid; place-items: center; font-size: 20px; font-weight: bold; }
#slider-container .cover { position: absolute; left: 0; top: 0; width: 100%; height: 100%; background-color: #fff; z-index: 110; }
</style>
<div id="slider-container">
<div id="demo"></div>
<div class="details" id="details-even">
<div class="place-box"><div class="text"></div></div>
<div class="title-box-1"><div class="title-1"></div></div>
<div class="title-box-2"><div class="title-2"></div></div>
<div class="desc"></div>
<div class="cta">
<button class="bookmark"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M6.32 2.577a49.255 49.255 0 0111.36 0c1.497.174 2.57 1.46 2.57 2.93V21l-7.165-3.583L4.835 21V5.507c0-1.47 1.073-2.756 2.57-2.93z" /></svg></button>
<button class="discover">Discover</button>
</div>
</div>
<div class="details" id="details-odd">
<div class="place-box"><div class="text"></div></div>
<div class="title-box-1"><div class="title-1"></div></div>
<div class="title-box-2"><div class="title-2"></div></div>
<div class="desc"></div>
<div class="cta">
<button class="bookmark"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M6.32 2.577a49.255 49.255 0 0111.36 0c1.497.174 2.57 1.46 2.57 2.93V21l-7.165-3.583L4.835 21V5.507c0-1.47 1.073-2.756 2.57-2.93z" /></svg></button>
<button class="discover">Discover</button>
</div>
</div>
<div class="pagination">
<div id="prev" class="arrow"><svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.75 19.5L8.25 12l7.5-7.5" /></svg></div>
<div id="next" class="arrow"><svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.25 4.5l7.5 7.5-7.5 7.5" /></svg></div>
<div class="progress-sub-container">
<div class="progress-sub-background"><div class="progress-sub-foreground"></div></div>
</div>
<div class="slide-numbers" id="slide-numbers"></div>
</div>
<div class="cover"></div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script>
(function() {
const data = [
{ place:'Switzerland Alps', title:'SAINT', title2:'ANTONIEN', description:'Tucked away in the Switzerland Alps, Saint Antönien offers an idyllic retreat.', image:'https://assets.codepen.io/3685267/timed-cards-1.jpg' },
{ place:'Japan Alps', title:'NAGANO', title2:'PREFECTURE', description:'Nagano Prefecture, set within the majestic Japan Alps, is cultural treasure.', image:'https://assets.codepen.io/3685267/timed-cards-2.jpg' },
{ place:'Sahara Desert', title:'MARRAKECH', title2:'MEROUGA', description:'The journey from the vibrant souks of Marrakech to the tranquil sands.', image:'https://assets.codepen.io/3685267/timed-cards-3.jpg' },
{ place:'Sierra Nevada', title:'YOSEMITE', title2:'NATIONAL PARK', description:'Yosemite National Park is a showcase of the American wilderness.', image:'https://assets.codepen.io/3685267/timed-cards-4.jpg' },
{ place:'Tarifa - Spain', title:'LOS LANCES', title2:'BEACH', description:'Los Lances Beach in Tarifa is a coastal paradise known for winds.', image:'https://assets.codepen.io/3685267/timed-cards-5.jpg' },
{ place:'Cappadocia', title:'GÖREME', title2:'VALLEY', description:'Göreme Valley in Cappadocia is a historical marvel with unique geology.', image:'https://assets.codepen.io/3685267/timed-cards-6.jpg' }
];
const _ = (id) => document.getElementById(id);
const container = _('slider-container');
const demo = _('demo');
let order = [0, 1, 2, 3, 4, 5];
let detailsEven = true;
let isAnimating = false;
let autoPlayTimeout;
const cardWidth = 120;
const cardHeight = 180;
const gap = 20;
const numberSize = 30;
const ease = "sine.inOut";
demo.innerHTML = data.map((i, index) => `<div class="card" id="card${index}" style="background-image:url(${i.image})"></div>`).join('') +
data.map((i, index) => `<div class="card-content" id="card-content-${index}"><div class="content-start"></div><div class="content-place">${i.place}</div><div class="content-title-1">${i.title}</div><div class="content-title-2">${i.title2}</div></div>`).join('');
_('slide-numbers').innerHTML = data.map((_, index) => `<div class="item" id="slide-item-${index}">${index + 1}</div>`).join('');
function init() {
const [active, ...rest] = order;
const detailsActive = detailsEven ? "#details-even" : "#details-odd";
const detailsInactive = detailsEven ? "#details-odd" : "#details-even";
const width = container.offsetWidth;
const height = container.offsetHeight;
const offsetTop = height - 210;
const offsetLeft = width - 450;
gsap.set(detailsInactive, { opacity: 0, visibility: 'hidden', zIndex: 12 });
updateDetails(detailsActive, active);
gsap.set(`#card${active}`, { x: 0, y: 0, width: width, height: height, zIndex: 10 });
gsap.set(`#card-content-${active}`, { opacity: 0 });
gsap.set(detailsActive, { opacity: 0, x: -50, zIndex: 80, visibility: 'visible' });
rest.forEach((i, index) => {
gsap.set(`#card${i}`, { x: offsetLeft + index * (cardWidth + gap), y: offsetTop, width: cardWidth, height: cardHeight, zIndex: 30, borderRadius: 8 });
gsap.set(`#card-content-${i}`, { x: offsetLeft + index * (cardWidth + gap), y: offsetTop + cardHeight - 95, zIndex: 40 });
gsap.set(`#slide-item-${i}`, { x: (index + 1) * numberSize });
});
gsap.to(".cover", { x: width + 100, duration: 0.8, ease, onComplete: () => { startAutoPlay(); } });
gsap.to(detailsActive, { opacity: 1, x: 0, ease, delay: 0.4 });
_('next').addEventListener('click', () => handleNav(1));
_('prev').addEventListener('click', () => handleNav(-1));
}
function updateDetails(id, index) {
const item = data[index];
const el = document.querySelector(id);
el.querySelector('.text').textContent = item.place;
el.querySelector('.title-1').textContent = item.title;
el.querySelector('.title-2').textContent = item.title2;
el.querySelector('.desc').textContent = item.description;
}
async function step(direction = 1) {
if (isAnimating) return;
isAnimating = true;
if (direction === 1) { order.push(order.shift()); } else { order.unshift(order.pop()); }
detailsEven = !detailsEven;
const detailsActive = detailsEven ? "#details-even" : "#details-odd";
const detailsInactive = detailsEven ? "#details-odd" : "#details-even";
const [active, ...rest] = order;
updateDetails(detailsActive, active);
gsap.set(detailsActive, { zIndex: 80, opacity: 0, visibility: 'visible' });
gsap.to(detailsActive, { opacity: 1, delay: 0.2, ease });
gsap.to(detailsInactive, { opacity: 0, zIndex: 12, onComplete: () => { gsap.set(detailsInactive, { visibility: 'hidden' }); }});
const width = container.offsetWidth;
const height = container.offsetHeight;
const offsetTop = height - 210;
const offsetLeft = width - 450;
gsap.to(`#card${active}`, { x: 0, y: 0, width: width, height: height, borderRadius: 0, ease, duration: 0.7, zIndex: 20 });
gsap.to(`#card-content-${active}`, { opacity: 0, duration: 0.2 });
gsap.to(".progress-sub-foreground", { width: `${(100 / data.length) * (active + 1)}%`, ease });
rest.forEach((i, index) => {
const xNew = offsetLeft + index * (cardWidth + gap);
gsap.to(`#card${i}`, { x: xNew, y: offsetTop, width: cardWidth, height: cardHeight, borderRadius: 8, ease, zIndex: 30 });
gsap.to(`#card-content-${i}`, { x: xNew, y: offsetTop + cardHeight - 95, opacity: 1, ease, zIndex: 40 });
gsap.to(`#slide-item-${i}`, { x: (index + 1) * numberSize, ease });
});
setTimeout(() => { isAnimating = false; }, 1000);
}
function handleNav(dir) { clearTimeout(autoPlayTimeout); step(dir); startAutoPlay(); }
function startAutoPlay() { autoPlayTimeout = setTimeout(() => { step(1); startAutoPlay(); }, 3400); }
let loaded = 0;
data.forEach(d => {
const img = new Image();
img.src = d.image;
img.onload = () => { if(++loaded === data.length) init(); };
});
})();
</script>




コメントを投稿