Есть ли лучший способ имитировать это, но без огромного раздувания DOM? < /P>
с логикой сброса в реальном времени или зеркальным клонированием? Я видел это на некоторых веб -сайтах, но я понятия не имею, как это можно сделать ... это какая -то библиотека или что -то особенное? Установка работает хорошо, включая автоматическую и бесконечную прокрутку. Перетаскивание с левого и правого края должно быть полностью отключено.
css
.review-drag-zone {
position: absolute;
top: 0;
left: 25%;
width: 50%;
height: 100%;
z-index: 20;
cursor: grab;
pointer-events: auto;
}
js для области перетаскивания
(() => {
const wrapper = document.querySelector('.review-scroll-wrapper');
const dragZone = document.querySelector('.review-drag-zone');
const track = $('#scrollCards');
// Initially disable dragging globally
track.slick('slickSetOption', 'draggable', false, true);
// Enable only when hovering over center
dragZone.addEventListener('mouseenter', () => {
track.slick('slickSetOption', 'draggable', true, true);
});
dragZone.addEventListener('mouseleave', () => {
track.slick('slickSetOption', 'draggable', false, true);
});
})();
< /code>
Проблема:
Курсор Grab правильно отображается в середине.
MEET OUR AMAZING TEAM
json
[
{
"name": "Alice Novak",
"role": "COO of FutureTech",
"image": "images/alice.png",
"linkedin": "https://linkedin.com/in/reviewerLINKEDIN1",
"text": "Professional, fast and reliable results. Highly recommend."
},
{
"name": "Carlos Duran",
"role": "Founder of WebWave",
"image": "images/carlos.png",
"linkedin": "https://linkedin.com/in/reviewerLINKEDIN2",
"text": "Super helpful in scaling our outreach. Great comms."
},
{
"name": "Linda Park",
"role": "Lead at PixelNest",
"image": "images/linda.png",
"linkedin": "https://linkedin.com/in/reviewerLINKEDIN3",
"text": "Very creative! A lot of attention to detail in the work."
}
//Many more random examples below
]
< /code>
js < /strong> рабочее решение < /p>
// JSON Storage Bin, see: https://www.npoint.io
const dataURL = "https://api.npoint.io/0203fc45b524c37f2e1c"; // review.json
// Global flag to notify scroll script
window.reviewsReady = false;
fetch(dataURL)
.then(res => res.json())
.then(data => {
const track = document.getElementById('scrollCards');
track.innerHTML = ''; // clear any fallback
data.forEach(review => {
const card = document.createElement('div');
card.className = 'review-card';
card.innerHTML = `
${review.name}
${review.role}
${review.text}
`;
track.appendChild(card);
});
// Notify that reviews are loaded
window.reviewsReady = true;
})
.catch(err => console.error("Failed to load reviews:", err));
function initReviewScroller() {
const wrapper = document.querySelector('.review-scroll-wrapper');
const track = document.getElementById('scrollCards');
const originalCards = Array.from(track.children);
const MULTIPLIER = 20;
for (let i = 0; i < MULTIPLIER - 1; i++) {
originalCards.forEach(card => track.appendChild(card.cloneNode(true)));
}
const allCards = track.querySelectorAll('.review-card');
const cardWidth = allCards[0].offsetWidth + 30;
const totalCards = allCards.length;
const totalWidth = cardWidth * totalCards;
wrapper.scrollLeft = totalWidth / 2;
let isDragging = false;
let dragStartX = 0;
let scrollStart = 0;
let pauseAuto = false;
wrapper.addEventListener('mousedown', (e) => {
isDragging = true;
pauseAuto = true;
dragStartX = e.pageX;
scrollStart = wrapper.scrollLeft;
});
wrapper.addEventListener('mouseup', () => {
isDragging = false;
pauseAuto = false;
});
wrapper.addEventListener('mouseleave', () => {
isDragging = false;
pauseAuto = false;
});
wrapper.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const dx = e.pageX - dragStartX;
wrapper.scrollLeft = scrollStart - dx;
});
wrapper.addEventListener('scroll', () => {
const current = wrapper.scrollLeft;
if (current = totalWidth - wrapper.clientWidth - cardWidth) {
wrapper.scrollLeft -= totalWidth / 2;
}
});
function autoScrollStep() {
if (!pauseAuto) {
wrapper.scrollBy({
left: cardWidth,
behavior: 'smooth'
});
}
setTimeout(autoScrollStep, 5000);
}
setTimeout(autoScrollStep, 500);
}
// Wait for reviews to load
function waitForReviews() {
if (window.reviewsReady) {
initReviewScroller();
} else {
setTimeout(waitForReviews, 100);
}
}
waitForReviews();
css завершить
/*Reviews/Testimony Section*/
.reviews-section {
background-color: #0c1624; /* dark blue background */
color: white;
padding: 120px 20px;
/* text-align: right;
min-height: 250px; */
}
.reviews-container {
max-width: 1400px;
margin: -200px auto 0 auto; /* top -20px, horizontal auto, bottom 0 */
}
.review-title {
font-size: 45px;
font-weight: bold;
margin-bottom: 15px;
text-align: center;
font-family: 'Orbitron', sans-serif;
}
.review-title-gold {
color: #Efc900; /* bright gold */
}
.review-scroll-wrapper {
position: relative;
overflow: hidden;
cursor: grab;
overflow-x: auto;
scroll-behavior: smooth;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE/Edge */
mask-image: linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%);
-webkit-mask-image: linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%);
/* For Chrome, Safari */
&::-webkit-scrollbar {
display: none;
}
}
/* .review-scroll-wrapper:active {
cursor: grabbing;
} */
/* .card-holder {
display: flex;
gap: 30px;
width: max-content;
padding: 10px;
} */
/* Force Slick to respect your gap + card size */
.slick-track {
display: flex !important;
gap: 30px !important;
padding: 10px !important;
margin-left: 0 !important; /* prevent centering */
}
.slick-slide {
height: auto !important;
display: flex !important;
justify-content: center; /* optional */
}
/* Optional: if Slick wrapper centers */
#scrollCards.slick-slider {
text-align: left !important;
}
/* Optional: Hide flicker while Slick loads */
#scrollCards {
visibility: visible;
opacity: 1;
}
.review-scroll-wrapper::before,
.review-scroll-wrapper::after {
content: "";
position: absolute;
top: 0;
width: 60px;
height: 100%;
z-index: 2;
pointer-events: none;
}
.review-scroll-wrapper::before {
left: 0;
background: linear-gradient(to right, #0c1624, transparent);
}
.review-scroll-wrapper::after {
right: 0;
background: linear-gradient(to left, #0c1624, transparent);
}
.review-drag-zone {
position: absolute;
top: 0;
left: 25%;
width: 50%;
height: 100%;
z-index: 20;
cursor: grab;
pointer-events: auto;
}
.review-drag-zone:active {
cursor: grabbing;
}
.review-scroll-wrapper .slick-track {
cursor: default !important;
}
.review-card {
flex: 0 0 auto; /* Prevent shrinking, enforce fixed width */
width: 320px;
background-color: #0d191f;
border: 1px solid #Efc900;
border-radius: 20px;
padding: 20px;
box-sizing: border-box;
display: flex;
flex-direction: column;
margin-top: 40px;
color: white;
}
/* Main layout: pic, text, and icon */
.review-header {
display: flex;
align-items: flex-start;
position: relative;
}
/* Profile pic */
.linkedin-pic {
flex-shrink: 0;
}
.pic-prof {
width: 50px;
height: 50px;
border-radius: 50%;
}
/* Name + role beside image */
.review-info {
margin-left: 15px;
}
/* LinkedIn icon on top right */
.linkpic-url {
position: absolute;
top: 0;
right: 0;
}
.linkedin-icon {
width: 50px;
height: 50px;
}
/* Name */
.review-name {
font-size: 18px;
font-weight: bold;
margin: 0;
font-family: 'Times New Roman', sans-serif;
}
/* Role */
.review-role {
font-size: 12px;
font-weight: bold;
margin-top: 5px;
color: #Efc900;
font-family: 'Times New Roman', sans-serif;
}
/* Quote */
.review-text {
font-size: 16px;
margin-top: 25px;
color: #bbb;
font-family: 'Times New Roman', serif;
}
/*________________________________________*/
Подробнее здесь: https://stackoverflow.com/questions/796 ... ched-conte
Мобильная версия