tdpeuter/components/my-marquee.js

166 lines
No EOL
4.8 KiB
JavaScript

function getTemplate() {
const template = document.createElement('template');
template.id = 'my-marquee-template';
template.innerHTML = `
<style>
.marquee-wrapper {
mask-image: linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%);
}
.marquee {
overflow: hidden;
overflow-x: auto;
position: relative;
scrollbar-width: none;
}
.marquee-content {
display: flex;
gap: 8px;
width: max-content;
animation: scroll-left 0s linear infinite;
}
.marquee-content:hover {
animation-play-state: paused;
}
/* Animation */
@keyframes scroll-left {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-25%);
}
}
@keyframes scroll-right {
0% {
transform: translateX(0);
}
100% {
transform: translateX(25%);
}
}
</style>
<div class="marquee-wrapper">
<div class="marquee">
<div class="marquee-content">
<slot></slot>
</div>
</div>
</div>
`;
return template;
}
class MyMarquee extends HTMLElement {
static observedAttributes = ['speed', 'direction'];
constructor() {
super();
const wrapper = getTemplate().content.cloneNode(true);
const marquee = wrapper.querySelector('.marquee');
const content = wrapper.querySelector('.marquee-content');
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.appendChild(wrapper);
this.marquee = marquee;
this.content = content;
this.repetitions = 1;
this.speed = 500; // Default speed
this.direction = 'left'; // Default direction
window.addEventListener('load', this.onLoad.bind(this));
this.marquee.addEventListener('scroll', this.handleScroll.bind(this), { passive: true });
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'speed') {
this.speed = parseInt(newValue, 10) || this.speed; // Default to 100 if not set
this.updateAnimation();
} else if (name === 'direction') {
this.direction = newValue || this.direction; // Default to 'left' if not set
this.updateAnimation();
}
}
onLoad() {
const slot = this.content.querySelector('slot');
const slottedElements = slot.assignedNodes({ flatten: true }).filter(node => node.nodeType === Node.ELEMENT_NODE);
this.content.innerHTML = slottedElements.map(el => el.outerHTML).join('');
requestAnimationFrame(() => {
const singleWidth = this.content.scrollWidth;
// Calculate the needed repetitions
let repetitions = Math.ceil(window.innerWidth / singleWidth) + 1;
this.repetitions = repetitions * 4; // Double the repetitions for a seamless effect
if (debug) {
console.log(`Single content width: ${singleWidth}, Repetitions: ${this.repetitions}`);
}
this.content.innerHTML = this.content.innerHTML.repeat(this.repetitions);
this.repeatedWidth = singleWidth * this.repetitions;
this.updateAnimation();
// Set initial scroll position
this.marquee.scrollLeft = (this.repeatedWidth - window.innerWidth) / 2;
});
}
updateAnimation() {
// Set the animation duration
const duration = (this.repeatedWidth / this.speed) * 1000; // Convert to milliseconds
this.content.style.animationDuration = `${duration}ms`;
// Set the animation direction
console.assert(['left', 'right'].includes(this.direction))
this.content.style.animationName = `scroll-${this.direction}`;
if (debug) {
console.log(`Animation updated: Duration ${duration}ms, Direction ${this.direction}`);
}
}
handleScroll() {
const scrollStep = this.content.scrollWidth * 2 / this.repetitions;
const currentScroll = this.marquee.scrollLeft;
// Handle scrolling to the right
if (currentScroll >= this.content.scrollWidth * 0.7) {
if (debug) {
console.log(`Jumping back by ${scrollStep}`);
ping();
}
this.marquee.scrollLeft -= scrollStep;
}
// Handle scrolling to the left
if (currentScroll <= this.content.scrollWidth * 0.3) {
if (debug) {
console.log(`Jumping forward by ${scrollStep}`);
ping();
}
this.marquee.scrollLeft += scrollStep;
}
}
}
function ping() {
const ping = new Audio('../assets/sounds/cowbell.mp3');
ping.play().catch(error => {
console.error('Error playing sound:', error);
});
}
customElements.define('my-marquee', MyMarquee);