ההמרה מדף נחיתה ירדה ב-15%. הניתוחים מראים שמשתמשים גוללים במהירות בין הבלוקים מבלי לקרוא את המסרים המרכזיים. אנו מציעים פתרון: גלילה סקשנלית עם עצירה כפויה על כל סלייד, באמצעות CSS Scroll Snap טבעי. מנגנון זה פועל ברמת ה-compositor של הדפדפן — ללא ספריות נוספות, 60 fps, חלק גם במובייל. Core Web Vitals משתפרים: LCP יורד ב-25%, CLS נשאר אפס, INP יורד ב-30% בזכות הפחתת עומס על ה-main thread.
במשך 7+ שנים, יישמנו scroll-snap בעשרות פרויקטים: מדפי נחיתה ועד אתרי מסחר אלקטרוני עם קרוסלות אופקיות. קבלו ייעוץ — אנו מעריכים את המשימה שלכם תוך 24 שעות.
מתי יש צורך בגלילה סקשנלית?
גלילה סקשנלית עם CSS Scroll Snap אידיאלית לבלוקים של הצגה רציפה: פורטפוליו, שירותים, יתרונות. אם המשתמשים חייבים לצרוך מידע שלב אחר שלב, דפוס זה מגביר מעורבות והמרה ב-15–25%, במיוחד במובייל.
יישום בסיסי של CSS Scroll Snap
הגישה הפשוטה ביותר: גלילה אנכית לפי סקשנים. הקונטיינר מקבל scroll-snap-type: y mandatory;, כל סקשן מקבל scroll-snap-align: start; ו-scroll-snap-stop: always;. המשתמש לא יכול לעצור בין סקשנים. פרטים נוספים ב-תיעוד MDN.
.scroll-container {
height: 100vh;
overflow-y: scroll;
scroll-snap-type: y mandatory;
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
}
.section {
height: 100vh;
scroll-snap-align: start;
scroll-snap-stop: always;
}
/* Горизонтальный слайдер */
.slider-container {
display: flex;
overflow-x: scroll;
scroll-snap-type: x mandatory;
gap: 16px;
padding: 0 16px;
scrollbar-width: none;
}
.slider-container::-webkit-scrollbar {
display: none;
}
.slide {
flex-shrink: 0;
width: 300px;
scroll-snap-align: start;
}ברכיב React, השתמשו ב-inline styles לתוכן דינמי:
// components/SectionScroll.tsx
export function SectionScroll({ sections }: { sections: React.ReactNode[] }) {
return (
<div
className="h-screen overflow-y-scroll"
style={{
scrollSnapType: 'y mandatory',
scrollBehavior: 'smooth',
}}
>
{sections.map((section, i) => (
<section
key={i}
className="h-screen flex items-center justify-center"
style={{
scrollSnapAlign: 'start',
scrollSnapStop: 'always',
}}
>
{section}
</section>
))}
</div>
)
} למה CSS Scroll Snap מהיר יותר מ-JavaScript?
ספריות JavaScript (Intersection Observer, גלילה עם throttle) מעמיסות על ה-main thread, וגורמות לנפילת פריימים. CSS Scroll Snap פועל על ה-compositor thread — הדפדפן מחשב את ה-snapping לפני רינדור הפיקסלים. תוצאה: 60 fps יציב, 30–50% פחות שימוש במעבד. זמן פיתוח: 6 שעות במקום 3 ימים.
השוואה בין Mandatory ל-Proximity
| פרמטר | mandatory | proximity |
|---|---|---|
| Snapping | תמיד נצמד לנקודת ה-snap | נצמד רק אם הגלילה עוצרת ליד הנקודה |
| יישום | דפי נחיתה עם סקשנים בגובה שווה | עמודים ארוכים עם תוכן מגוון |
| סיכון | לא ניתן לגלול לאמצע הסקשן | ייתכן שלא יופעל בגלילה מהירה |
| המלצה | למצגות סקשן-אחר-סקשן | למאמרים וקטלוגים |
אילו מדדים משפר CSS Scroll Snap?
יישום scroll-snap משפיע ישירות על Web Vitals. LCP יורד ב-25–30% בזכות היעדר repaints במהלך הגלילה. CLS נשאר יציב. INP יורד ב-30% כי ה-compositor thread נשאר לא חסום. לפי Google, אתרים עם גלילה טבעית מקבלים דירוג גבוה יותר בחיפוש במובייל.
היישום שלנו במפתח-ביד
דוגמה מפרויקט אחרון: דף נחיתה לסטארטאפ fintech עם 6 בלוקי תוכן. טכנולוגיות: React 18, Next.js 14, TypeScript, Tailwind CSS. עיצבנו גלילה מלאה עם נקודות ניווט ואנימציות כניסה המופעלות על ידי Intersection Observer.
כדי לעקוב אחר הסקשן הפעיל, אנו משתמשים ב-hook .scroll-container { height: 100vh; overflow-y: scroll; scroll-snap-type: y mandatory; scroll-behavior: smooth; -webkit-overflow-scrolling: touch; } .section { height: 100vh; scroll-snap-align: start; scroll-snap-stop: always; } /* Горизонтальный слайдер */ .slider-container { display: flex; overflow-x: scroll; scroll-snap-type: x mandatory; gap: 16px; padding: 0 16px; scrollbar-width: none; } .slider-container::-webkit-scrollbar { display: none; } .slide { flex-shrink: 0; width: 300px; scroll-snap-align: start; } המחשב את אינדקס הסלייד הנוכחי מ-// components/SectionScroll.tsx export function SectionScroll({ sections }: { sections: React.ReactNode[] }) { return ( <div className="h-screen overflow-y-scroll" style={{ scrollSnapType: 'y mandatory', scrollBehavior: 'smooth', }} > {sections.map((section, i) => ( <section key={i} className="h-screen flex items-center justify-center" style={{ scrollSnapAlign: 'start', scrollSnapStop: 'always', }} > {section} </section> ))} </div> ) } . הניווט מופיע כפאנל שמאלי קבוע עם הדגשת הפריט הפעיל.
// hooks/useActiveSectionScroll.ts
import { useEffect, useRef, useState } from 'react'
export function useActiveSectionScroll(sectionCount: number) {
const [activeIndex, setActiveIndex] = useState(0)
const containerRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const container = containerRef.current
if (!container) return
const handleScroll = () => {
const { scrollTop, clientHeight } = container
const index = Math.round(scrollTop / clientHeight)
setActiveIndex(index)
}
container.addEventListener('scroll', handleScroll, { passive: true })
return () => container.removeEventListener('scroll', handleScroll)
}, [])
const scrollToSection = (index: number) => {
const container = containerRef.current
if (!container) return
container.scrollTo({
top: index * container.clientHeight,
behavior: 'smooth',
})
}
return { containerRef, activeIndex, scrollToSection }
} // components/FullPageScroll.tsx
import { useActiveSectionScroll } from '../hooks/useActiveSectionScroll'
const sections = [
{ id: 'hero', label: 'Главная', color: 'bg-blue-600' },
{ id: 'about', label: 'О нас', color: 'bg-purple-600' },
{ id: 'services', label: 'Услуги', color: 'bg-indigo-600' },
{ id: 'contact', label: 'Контакты', color: 'bg-pink-600' },
]
export function FullPageScroll() {
const { containerRef, activeIndex, scrollToSection } = useActiveSectionScroll(sections.length)
return (
<div className="relative">
<nav className="fixed right-6 top-1/2 -translate-y-1/2 z-50 flex flex-col gap-3">
{sections.map((section, i) => (
<button
key={section.id}
onClick={() => scrollToSection(i)}
className={`
w-3 h-3 rounded-full border-2 border-white transition-all duration-300
${activeIndex === i ? 'bg-white scale-125' : 'bg-transparent'}
`}
aria-label={`Перейти к ${section.label}`}
/>
))}
</nav>
<div
ref={containerRef}
className="h-screen overflow-y-scroll"
style={{ scrollSnapType: 'y mandatory' }}
>
{sections.map((section, i) => (
<section
key={section.id}
id={section.id}
className={`h-screen flex items-center justify-center ${section.color}`}
style={{ scrollSnapAlign: 'start', scrollSnapStop: 'always' }}
>
<h2 className="text-5xl font-bold text-white">{section.label}</h2>
</section>
))}
</div>
</div>
)
} תהליך העבודה
- אנליטיקה — סקירת מבנה דף הנחיתה, התנהגות משתמשים באמצעות heatmaps.
- עיצוב — בחירת סוג ה-snap, הוספת fallback לדפדפנים ישנים.
- סימון — HTML סמנטי, סגנונות עם
useActiveSectionScroll, שילוב React hooks. - בדיקות — אימות במכשירים אמיתיים (iOS, Android), מדידת LCP ו-CLS.
- השקה — פיצול bundles, שילוב CDN.
מלכודות iOS טיפוסיות
הבעיות העיקריות: גלילה אינרציאלית והתעלמות מ-scrollTop. פתרון — הוסיפו // hooks/useActiveSectionScroll.ts import { useEffect, useRef, useState } from 'react' export function useActiveSectionScroll(sectionCount: number) { const [activeIndex, setActiveIndex] = useState(0) const containerRef = useRef<HTMLDivElement>(null) useEffect(() => { const container = containerRef.current if (!container) return const handleScroll = () => { const { scrollTop, clientHeight } = container const index = Math.round(scrollTop / clientHeight) setActiveIndex(index) } container.addEventListener('scroll', handleScroll, { passive: true }) return () => container.removeEventListener('scroll', handleScroll) }, []) const scrollToSection = (index: number) => { const container = containerRef.current if (!container) return container.scrollTo({ top: index * container.clientHeight, behavior: 'smooth', }) } return { containerRef, activeIndex, scrollToSection } } , חסמו מומנטום באמצעות // components/FullPageScroll.tsx import { useActiveSectionScroll } from '../hooks/useActiveSectionScroll' const sections = [ { id: 'hero', label: 'Главная', color: 'bg-blue-600' }, { id: 'about', label: 'О нас', color: 'bg-purple-600' }, { id: 'services', label: 'Услуги', color: 'bg-indigo-600' }, { id: 'contact', label: 'Контакты', color: 'bg-pink-600' }, ] export function FullPageScroll() { const { containerRef, activeIndex, scrollToSection } = useActiveSectionScroll(sections.length) return ( <div className="relative"> <nav className="fixed right-6 top-1/2 -translate-y-1/2 z-50 flex flex-col gap-3"> {sections.map((section, i) => ( <button key={section.id} onClick={() => scrollToSection(i)} className={` w-3 h-3 rounded-full border-2 border-white transition-all duration-300 ${activeIndex === i ? 'bg-white scale-125' : 'bg-transparent'} `} aria-label={`Перейти к ${section.label}`} /> ))} </nav> <div ref={containerRef} className="h-screen overflow-y-scroll" style={{ scrollSnapType: 'y mandatory' }} > {sections.map((section, i) => ( <section key={section.id} id={section.id} className={`h-screen flex items-center justify-center ${section.color}`} style={{ scrollSnapAlign: 'start', scrollSnapStop: 'always' }} > <h2 className="text-5xl font-bold text-white">{section.label}</h2> </section> ))} </div> </div> ) } , והגדירו scroll-snap-type כדי למנוע pull-to-refresh.
- תמיד השתמשו ב-
scroll-snap-stop: alwaysבמקום-webkit-overflow-scrolling: touch. - לניווט בלחיצה, השתמשו ב-
overscroll-behavior: noneו-overflow-y: scroll;עםauto. - ודאו שכל סקשן בגובה 100vh בדיוק, אחרת ה-snapping עלול לתפקד לא נכון.
תוצרים
- גלילה אנכית סקשנלית עם
ref - פאנל נקודות ניווט עם אינדיקטור פעיל
- אנימציות כניסה באמצעות Intersection Observer (אופציונלי)
- קרוסלה אופקית עם
scrollTo - רספונסיביות למובייל וטיפול באירועי מגע
- בדיקות על iPhone, iPad, Android
- תיעוד מלא עם הערות קוד ומדריך שילוב
- תמיכה ל-30 יום לאחר ההשקה
לוחות זמנים טיפוסיים
יישום בסיסי (סקשנים אנכיים + ניווט): 4–6 שעות. מחזור מלא עם קרוסלה, אנימציות ורספונסיביות: 1–2 ימי עבודה.
סיכום
CSS Scroll Snap היא טכנולוגיה בוגרת לגלילה סקשנלית מהירה ואמינה. אנו משלבים אותה בכל פרויקט ומבטיחים חלקות בכל המכשירים. צרו קשר כדי להתחיל את דף הנחיתה שלכם עם גלילה מושלמת.







