166 lines
No EOL
4.8 KiB
JavaScript
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); |