האפקט שגורם לדף להרגיש מופק. seek בתגית וידאו לא מספיק לעמוד בקצב הגלילה, ולכן כאן סדרת תמונות שמצוירת לקנבס. הטעינה דו-שלבית: קודם כל פריים שמיני כדי שהרקע יגיב מיד, ואז השאר בקבוצות. רקע פריימים בגלילה הוא רכיב מתוך רכיבים של כליקס. מה שמורידים הוא רכיב שלם, גם כ-HTML עצמאי וגם כרכיב React. אין ספרייה חיצונית, אין שלב בנייה, ואם כליקס ייעלם מחר הקוד ימשיך לעבוד. מתאים לעמודים ולמסמכים שעוסקים בגלילה, קנבס ובלי ספריות. באותה סדרה יש גם שאלות נפוצות, ציר תהליך וקרוסלת המלצות.
רכיב · גלילה · קנבס · בלי ספריות
<!-- רקע פריימים לפי גלילה: להדביק לפני </body> -->
<div id="klix-scrub" aria-hidden="true">
<canvas></canvas>
<span class="scrim"></span>
</div>
<style>
#klix-scrub{position:fixed;inset:0;z-index:-10}
#klix-scrub canvas{width:100%;height:100%;display:block}
/* חושך מדורג לקריאות הטקסט שמעל */
#klix-scrub .scrim{position:absolute;inset:0;
background:linear-gradient(180deg,rgba(2,6,23,.7),rgba(2,6,23,.35) 45%,rgba(2,6,23,.75))}
</style>
<script>
(function () {
// ═══ ערכו כאן ═══
var FRAME_COUNT = 96;
var POSTER = '/frames/poster.webp';
function frameUrl(i) {
return '/frames/hero_' + String(i + 1).padStart(4, '0') + '.webp';
}
// ═════════════════
var wrap = document.getElementById('klix-scrub');
if (!wrap) return;
var canvas = wrap.querySelector('canvas');
var ctx = canvas.getContext('2d');
var images = new Array(FRAME_COUNT);
var rendered = -1, current = 0, target = 0, raf = 0, animating = false, disposed = false;
// הגלילה היא תנועה של המשתמש עצמו, ולכן ה-scrub נשאר פעיל גם במצב תנועה
// מופחתת. רק ההחלקה מבוטלת, כדי שהתמונה תיצמד ישירות לגלגלת.
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var poster = new Image();
poster.src = POSTER;
poster.onload = function () { if (rendered < 0) drawCover(poster); };
var dpr = Math.min(window.devicePixelRatio || 1, 2);
function resize() {
canvas.width = Math.round(window.innerWidth * dpr);
canvas.height = Math.round(window.innerHeight * dpr);
rendered = -2;
}
function drawCover(img) {
var cw = canvas.width, ch = canvas.height;
var scale = Math.max(cw / img.naturalWidth, ch / img.naturalHeight);
var w = img.naturalWidth * scale, h = img.naturalHeight * scale;
ctx.drawImage(img, (cw - w) / 2, (ch - h) / 2, w, h);
}
// הפריים הטעון הקרוב ביותר לאינדקס המבוקש, כדי שלא יהיו הבהובים
// בזמן שהשאר עוד בדרך
function nearestLoaded(idx) {
for (var d = 0; d < FRAME_COUNT; d++) {
var before = images[idx - d];
if (before && before.complete && before.naturalWidth) return before;
var after = images[idx + d];
if (after && after.complete && after.naturalWidth) return after;
}
return poster.complete && poster.naturalWidth ? poster : null;
}
// ה-rAF רץ רק כשיש מה לצייר, ונרגע כשהפריים מתייצב — חוסך סוללה
function tick() {
if (disposed) return;
current = reduced ? target : current + (target - current) * 0.16;
if (Math.abs(target - current) < 0.02) current = target;
var idx = Math.round(current);
if (idx !== rendered) {
var img = nearestLoaded(idx);
if (img) { drawCover(img); rendered = idx; }
}
if (current === target && rendered === idx) { animating = false; return; }
raf = requestAnimationFrame(tick);
}
function ensureTick() {
if (!animating && !disposed) { animating = true; raf = requestAnimationFrame(tick); }
}
function readScroll() {
var max = document.documentElement.scrollHeight - window.innerHeight;
var p = max > 0 ? Math.min(1, Math.max(0, window.scrollY / max)) : 0;
target = p * (FRAME_COUNT - 1);
ensureTick();
}
function load(i) {
return new Promise(function (resolve) {
var img = new Image();
img.onload = function () {
images[i] = img;
if (Math.round(current) === i || rendered < 0) { rendered = -2; ensureTick(); }
resolve();
};
img.onerror = function () { resolve(); };
img.src = frameUrl(i);
});
}
// טעינה דו-שלבית: קודם כל פריים שמיני, כדי שהרקע יגיב תוך שניות,
// ואז השאר בקבוצות כדי לא לחנוק את הרשת
function loadAll() {
var coarse = [];
for (var i = 0; i < FRAME_COUNT; i += 8) coarse.push(i);
coarse.push(FRAME_COUNT - 1);
Promise.all(coarse.map(load)).then(function () {
var rest = [];
for (var j = 0; j < FRAME_COUNT; j++) if (!images[j]) rest.push(j);
(function next(c) {
if (c >= rest.length || disposed) return;
Promise.all(rest.slice(c, c + 12).map(load)).then(function () { next(c + 12); });
})(0);
});
}
resize();
readScroll();
current = target;
window.addEventListener('resize', function () { resize(); ensureTick(); });
window.addEventListener('scroll', readScroll, { passive: true });
ensureTick();
loadAll();
})();
</script>כליקס לא הצליח להיטען
נסו קודם לרענן את העמוד. אם בדיוק עלתה גרסה חדשה, רענון פותר את זה מיד.
אם הרענון לא עזר, ייתכן שהדפדפן ישן מדי: כליקס מריץ את הכלים על המכשיר שלכם ולא בשרת, ולכן הוא זקוק לדפדפן מ-2022 ומעלה. מה עושים: לעדכן את Chrome או את Firefox, ולפתוח שוב את klixtools.com.