Anonymous
Как создать гладкий бесконечный горизонтальный свиток (цикл) с JSON-выноженным контентом в JavaScript? [закрыто]
Сообщение
Anonymous » 10 июл 2025, 20:23
Я строю веб -сайт портфеля, где я показываю обзоры клиентов в горизонтальном прокручиваемом разделе. Я динамически получаю обзорные карты из файла review.json. Когда я достигаю левой или правой, он резко сбрасывается или попадает в визуальный край, если я не клонирую содержание в 1000 раз (что, очевидно, отстает от браузера). Но нет никакой реальной бесконечной иллюзии, если я не использую 50–100+ клоны DOM, что не эффективно. Использование 20 клонов уже становится тяжелым. < /P>
Есть ли лучший способ имитировать это, но без огромного раздувания DOM? < /P>
с логикой сброса в реальном времени или зеркальным клонированием? Я видел это на некоторых веб -сайтах, но я понятия не имею, как это можно сделать ... это какая -то библиотека или что -то особенное?
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>
[b] code < /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 = `
[img]${review.image}[/img]
${review.name}
${review.role}
[url=${review.linkedin}]
[img]https://static.vecteezy.com/system/resources/previews/018/930/480/non_2x/linkedin-logo-linkedin-icon-transparent-free-png.png[/img]
[/url]
${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();< /code>
Review of our clients
css [/b]
Код: Выделить всё
/*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;
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;
}
.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);
}
/* .card-holder::-webkit-scrollbar {
height: 8px;
}
.card-holder::-webkit-scrollbar-thumb {
background-color: #ece316;
border-radius: 10px;
} */
.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;
}
также добавил CSS в соответствии с запросом ...
Подробнее здесь:
https://stackoverflow.com/questions/796 ... ched-conte
1752168205
Anonymous
Я строю веб -сайт портфеля, где я показываю обзоры клиентов в горизонтальном прокручиваемом разделе. Я динамически получаю обзорные карты из файла review.json. Когда я достигаю левой или правой, он резко сбрасывается или попадает в визуальный край, если я не клонирую содержание в 1000 раз (что, очевидно, отстает от браузера). Но нет никакой реальной бесконечной иллюзии, если я не использую 50–100+ клоны DOM, что не эффективно. Использование 20 клонов уже становится тяжелым. < /P> Есть ли лучший способ имитировать это, но без огромного раздувания DOM? < /P> с логикой сброса в реальном времени или зеркальным клонированием? Я видел это на некоторых веб -сайтах, но я понятия не имею, как это можно сделать ... это какая -то библиотека или что -то особенное? [b] json [/b] [code][ { "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> [b] code < /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 = ` [img]${review.image}[/img] ${review.name} ${review.role} [url=${review.linkedin}] [img]https://static.vecteezy.com/system/resources/previews/018/930/480/non_2x/linkedin-logo-linkedin-icon-transparent-free-png.png[/img] [/url] ${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();< /code> Review of our clients [/code] css [/b] [code]/*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; 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; } .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); } /* .card-holder::-webkit-scrollbar { height: 8px; } .card-holder::-webkit-scrollbar-thumb { background-color: #ece316; border-radius: 10px; } */ .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; } [/code] также добавил CSS в соответствии с запросом ... Подробнее здесь: [url]https://stackoverflow.com/questions/79696453/how-to-create-a-smooth-infinite-horizontal-scroll-loop-with-json-fetched-conte[/url]