body
section.section
.grid
.section-img
img(src="https://picsum.photos/400/400?random=1", alt="")
.section-text
|Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione molestias itaque ducimus quasi. At nam quisquam atque neque autem, facere architecto. Explicabo porro tempora corporis quos obcaecati perferendis doloribus perspiciatis.
.marquee(data-marquee-speed="60" data-marquee-direction="right")
.marquee-wrapper
.marquee-content
- for (var i = 0; i < 5; i++)
.marquee-item
.content-img
img(src=`https://picsum.photos/id/${i+100}/600/400`, alt="")
section.section
.grid
.section-text
|Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione molestias itaque ducimus quasi. At nam quisquam atque neque autem, facere architecto. Explicabo porro tempora corporis quos obcaecati perferendis doloribus perspiciatis.
.section-img
img(src="https://picsum.photos/400/400?random=2", alt="")
.marquee(data-marquee-speed="60")
.marquee-wrapper
.marquee-content
.marquee-item
.content-text
|SAMPLE TEXT
section.section
.grid
.section-img
img(src="https://picsum.photos/400/400?random=1", alt="")
.section-text
|Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione molestias itaque ducimus quasi. At nam quisquam atque neque autem, facere architecto. Explicabo porro tempora corporis quos obcaecati perferendis doloribus perspiciatis.
.marquee(data-marquee-speed="60" data-marquee-hover="true")
.marquee-wrapper
.marquee-content
- for (var i = 0; i < 5; i++)
.marquee-item
.content-img
img(src=`https://picsum.photos/id/${i+200}/600/400`, alt="")
section.section
.grid
.section-text
|Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione molestias itaque ducimus quasi. At nam quisquam atque neque autem, facere architecto. Explicabo porro tempora corporis quos obcaecati perferendis doloribus perspiciatis.
.section-img
img(src="https://picsum.photos/400/400?random=2", alt="")
.marquee(data-marquee-speed="60" data-marquee-direction="right" data-marquee-hover="true")
.marquee-wrapper
.marquee-content
.marquee-item
.content-text
|SAMPLE TEXT
View Compiled
* {
margin: 0;
padding: 0;
}
body {
background: #333;
color: #eee;
padding-bottom: 50vh;
}
img {
width: 100%;
height: auto;
}
.section {
height: 60vh;
display: flex;
justify-content: center;
align-items: center;
max-width: 1200px;
margin-inline: auto;
padding-inline: 40px;
margin-block: 120px;
}
.grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 40px;
align-items: center;
}
.marquee {
overflow-x: hidden;
}
.marquee-wrapper {
display: flex;
gap: 24px;
}
.marquee-content {
display: flex;
gap: 24px;
}
.content-img {
width: 320px;
flex-shrink: 0;
}
.content-text {
font-weight: 700;
font-size: 60px;
white-space: nowrap;
}
View Compiled
class Marquee {
constructor() {
this.els = document.querySelectorAll('.marquee');
console.log(this.els);
if (!this.els) return;
this.init()
}
init() {
this.els.forEach(el => {
// 要素が非表示の場合は処理をスキップ
if (getComputedStyle(el).display === 'none') return;
const options = {
speed: el.dataset.marqueeSpeed || 60,
direction: el.dataset.marqueeDirection || 'left',
pauseOnHover: el.dataset.marqueeHover === 'true'
};
this.Marquee(el, options);
})
}
Marquee(el, options) {
const { speed, direction, pauseOnHover } = options;
const wrapper = el.querySelector('.marquee-wrapper');
const content = el.querySelector('.marquee-content');
this.appendContent(content, wrapper);
this.updateWrapperWidth(content, wrapper);
const animation = this.Animation(wrapper, speed, direction);
this.hoverEvent(el, animation, pauseOnHover);
this.observerEvent(el, animation);
}
updateWrapperWidth(content, wrap) {
const contentWidth = content.getBoundingClientRect().width;
console.log(contentWidth);
if (contentWidth === 0) return;
const gap = parseInt(getComputedStyle(content).columnGap);
wrap.style.width = `${contentWidth + gap}px`;
}
appendClone(content, wrap) {
const clone = content.cloneNode(true);
wrap.appendChild(clone);
}
appendContent(content, wrap) {
const innerWidth = window.innerWidth;
const contentWidth = content.getBoundingClientRect().width;
this.appendClone(content, wrap);
if (contentWidth < innerWidth) {
const numClones = Math.ceil(innerWidth / contentWidth);
for (let i = 0; i < numClones; i++) {
this.appendClone(content, wrap);
}
}
}
Animation(wrap, speed, direction) {
const wrapWidth = wrap.getBoundingClientRect().width;
if (wrapWidth === 0) return;
const keyframes = direction === 'left' ?
[{ translate: '0 0' }, { translate: '-100% 0' }] :
[{ translate: '-100% 0' }, { translate: '0 0' }];
const options = {
duration: (wrapWidth / speed) * 1000,
iterations: Infinity,
}
return wrap.animate(keyframes, options);
}
hoverEvent(el, animation, hasOpt) {
if (!hasOpt) return;
el.addEventListener('mouseenter', () => animation.pause());
el.addEventListener('mouseleave', () => animation.play());
}
observerEvent(el, animation) {
const observerOptions = {
root: null,
threshold: 0,
};
const observer = new IntersectionObserver((entry) => {
entry[0].isIntersecting ? animation.play() : animation.pause();
}, observerOptions);
observer.observe(el);
}
}
const marquee = new Marquee();
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.