Open TrdHuy opened 3 weeks ago
class HorImageContainer extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
document.documentElement.style.setProperty('--scroll-bar-height', '8px');
document.documentElement.style.setProperty('--scroll-bar-track-bg', '#f1f1f1');
document.documentElement.style.setProperty('--scroll-bar-thumb-bg', '#888');
document.documentElement.style.setProperty('--scroll-bar-thumb-border-rad', '10px');
document.documentElement.style.setProperty('--scroll-bar-track-border-rad', '10px');
document.documentElement.style.setProperty('--scroll-bar-thumb-bg-hover', '#555');
document.documentElement.style.setProperty('--scroll-bar-margin', '10px');
document.documentElement.style.setProperty('--wheel-scroll-velocity', '4');
document.documentElement.style.setProperty('--disable-wheel-scroll', '0');
const scrollPanel = document.createElement('div');
scrollPanel.className = 'scroll-panel';
scrollPanel.style.position = 'relative';
scrollPanel.style.width = '100%';
scrollPanel.style.overflow = 'hidden';
scrollPanel.style.cursor = 'grab';
scrollPanel.style.userSelect = 'none';
this.imageContainer = document.createElement('div');
const imageContainer = this.imageContainer;
imageContainer.className = 'image-container';
imageContainer.style.display = 'flex';
imageContainer.style.overflowX = 'hidden';
imageContainer.style.whiteSpace = 'nowrap';
imageContainer.style.padding = '10px';
imageContainer.style.scrollBehavior = 'smooth';
// Nhập nội dung từ slot
const slot = document.createElement('slot');
imageContainer.appendChild(slot);
// Nút mũi tên trái
const leftButton = document.createElement('button');
leftButton.className = 'arrow-button arrow-left';
leftButton.innerHTML = '‹';
leftButton.style.position = 'absolute';
leftButton.style.top = '50%';
leftButton.style.transform = 'translateY(-50%)';
leftButton.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
leftButton.style.color = 'white';
leftButton.style.border = 'none';
leftButton.style.padding = '10px';
leftButton.style.cursor = 'pointer';
leftButton.style.display = 'none';
leftButton.style.zIndex = '1';
leftButton.style.left = '0';
leftButton.onclick = () => this.scrollLeft();
// Nút mũi tên phải
const rightButton = document.createElement('button');
rightButton.className = 'arrow-button arrow-right';
rightButton.innerHTML = '›';
rightButton.style.position = 'absolute';
rightButton.style.top = '50%';
rightButton.style.transform = 'translateY(-50%)';
rightButton.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
rightButton.style.color = 'white';
rightButton.style.border = 'none';
rightButton.style.padding = '10px';
rightButton.style.cursor = 'pointer';
rightButton.style.display = 'none';
rightButton.style.zIndex = '1';
rightButton.style.right = '0';
rightButton.onclick = () => this.scrollRight();
// Thêm các phần tử vào panel cuộn
scrollPanel.appendChild(imageContainer);
scrollPanel.appendChild(leftButton);
scrollPanel.appendChild(rightButton);
const style = document.createElement('style');
style.textContent = `
:host{display: block;}
.image-container::-webkit-scrollbar{height:var(--scroll-bar-height);}
.image-container::-webkit-scrollbar-track{background:var(--scroll-bar-track-bg);border-radius:var(--scroll-bar-track-border-rad);margin:var(--scroll-bar-margin);}
.image-container::-webkit-scrollbar-thumb{background:var(--scroll-bar-thumb-bg);border-radius:var(--scroll-bar-thumb-border-rad);}
.image-container::-webkit-scrollbar-thumb:hover{background: #555;}`
// Thêm panel cuộn vào shadow DOM
shadowRoot.append(style, scrollPanel);
// Sự kiện kéo để cuộn
let isDown = false;
let startX;
let scrollLeft;
scrollPanel.addEventListener('mousedown', (e) => {
isDown = true;
scrollPanel.classList.add('active');
startX = e.pageX - imageContainer.offsetLeft;
scrollLeft = imageContainer.scrollLeft;
imageContainer.style.pointerEvents = 'none';
});
scrollPanel.addEventListener('mouseleave', () => {
isDown = false;
scrollPanel.classList.remove('active');
imageContainer.style.pointerEvents = 'all';
});
scrollPanel.addEventListener('mouseup', () => {
isDown = false;
scrollPanel.classList.remove('active');
imageContainer.style.pointerEvents = 'all';
});
scrollPanel.addEventListener('mousemove', (e) => {
if (!isDown) return;
e.preventDefault();
const x = e.pageX - imageContainer.offsetLeft;
const walk = (x - startX) * 2;
imageContainer.scrollLeft = scrollLeft - walk;
});
// Cuộn bằng chuột
scrollPanel.addEventListener('wheel', (e) => {
const isDisableWheelScrollProp = getComputedStyle(this).getPropertyValue('--disable-wheel-scroll').trim();
const isDisableWheelScroll = parseInt(isDisableWheelScrollProp);
const scrollVelocity = getComputedStyle(this).getPropertyValue('--wheel-scroll-velocity').trim();
const velocity = parseFloat(scrollVelocity);
if(isDisableWheelScroll == 0){
e.preventDefault();
imageContainer.scrollLeft += e.deltaY * velocity;
}
});
// Hiển thị nút khi hover vào scrollPanel
scrollPanel.addEventListener('mouseover', () => {
imageContainer.style.overflowX = 'auto';
if (imageContainer.scrollWidth > imageContainer.clientWidth) {
leftButton.style.display = 'block';
rightButton.style.display = 'block';
}
});
scrollPanel.addEventListener('mouseout', () => {
imageContainer.style.overflowX = 'hidden';
leftButton.style.display = 'none';
rightButton.style.display = 'none';
});
}
scrollLeft() {
const imageContainer = this.imageContainer;
imageContainer.scrollBy({
left: -300,
behavior: 'smooth'
});
}
scrollRight() {
const imageContainer = this.imageContainer;
imageContainer.scrollBy({
left: 300,
behavior: 'smooth'
});
}
}
customElements.define('hor-image-container', HorImageContainer);
class LoadingImage extends HTMLElement {
constructor() {
super();
this.host = this.attachShadow({ mode: 'open' });
document.documentElement.style.setProperty('--spinner-container-width', '300px');
document.documentElement.style.setProperty('--spinner-color', '#fff');
const shadowRoot = this.host;
shadowRoot.host.style.overflow = 'clip';
shadowRoot.host.style.display = 'flex';
shadowRoot.host.style.justifyContent = 'center';
shadowRoot.host.style.alignItems = 'center';
// Tạo container
this.container = document.createElement('div');
const container = this.container;
container.className = 'image-container';
// Tạo spinner
this.spinerContainer = document.createElement('div');
this.spinerContainer.style.width = 'var(--spinner-container-width)';
this.spinerContainer.style.top = '50%';
this.spinerContainer.style.display = 'flex';
this.spinerContainer.style.justifyContent = 'center';
this.spinner = document.createElement('div');
const spinner = this.spinner;
spinner.style.border = '4px solid rgba(0, 0, 0, 0.2)';
spinner.style.borderLeftColor = 'var(--spinner-color)';
spinner.style.borderRadius = '50%';
spinner.style.width = '50px';
spinner.style.height = '50px';
spinner.style.zIndex = '10';
spinner.style.animation = 'spin 1s linear infinite';
// Tạo thẻ img
this.img = document.createElement('img');
const img = this.img;
img.style.maxWidth = '100%';
img.style.maxHeight = '100%';
img.style.display = 'none';
// Đặt thuộc tính src từ thuộc tính của custom element
this.imgSrc = this.getAttribute('src');
const altText = this.getAttribute('alt');
img.alt = altText;
img.onload = () => {
spinner.style.display = 'none';
img.style.display = 'block';
// Cập nhật tỷ lệ của container dựa trên tỷ lệ của ảnh
const aspectRatio = img.naturalWidth / img.naturalHeight;
const parentWidth = 0;
const parentHeight = shadowRoot.host.clientHeight;
if (parentWidth != 0 && parentHeight != 0) {
if (parentWidth / parentHeight > aspectRatio) {
container.style.height = `${parentHeight}px`;
container.style.width = `${parentHeight * aspectRatio}px`;
} else {
container.style.width = `${parentWidth}px`;
container.style.height = `${parentWidth / aspectRatio}px`;
}
} else if (parentHeight != 0) {
container.style.height = `${parentHeight}px`;
container.style.width = `${parentHeight * aspectRatio}px`;
}
};
img.onerror = () => {
spinner.style.display = 'none';
console.error('Image failed to load');
};
img.style.display = 'none';
this.spinerContainer.appendChild(spinner);
container.appendChild(this.spinerContainer);
container.appendChild(img);
const style = document.createElement('style');
style.textContent = `@keyframes spin{0%{transform:rotate(0deg);}100% {transform: rotate(360deg);}}`;
shadowRoot.append(style, container);
}
connectedCallback() {
const customContainer = this.closest('hor-image-container');
// Nếu ở bên trong custom-image-container thì apply cơ chế lazy load theo IntersectionObserver
if (customContainer) {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
setTimeout(() => {
this.img.src = this.imgSrc;
}, 2000); // Delay for demonstration, can be removed
observer.unobserve(this);
}
});
}, {
root: null, // set null thì element sẽ dựa theo kích thước cửa sổ
threshold: 0.1 // 10% of the image is visible
});
observer.observe(this);
} else {
this.img.src = this.imgSrc;
}
}
}
customElements.define('loading-image', LoadingImage);
249537670DB03870A206C3933EB9B19BBB515B75D02A6494FF2BC538427ACB02