diff --git a/mobile/mobile-form-patterns.md b/mobile/mobile-form-patterns.md new file mode 100644 index 0000000..659e8d7 --- /dev/null +++ b/mobile/mobile-form-patterns.md @@ -0,0 +1,422 @@ +--- +title: Mobile Form Patterns +created: 2026-07-03 +updated: 2026-07-03 +type: system +tags: [mobile, forms, input, validation, UX, conversion] +sources: + - https://www.formCreatorai.com/mobile-form-design-patterns-maximize-completion + - https://subux.pro/guides/article/input-field-types-and-usage + - https://askusers.org/en/blog/mobile-form-optimization-completion-rates + - https://www.getworkform.com/blog/mobile-form-design-tips + - https://tinyform.com/post/mobile-form-statistics-ux-trends + - https://www.reform.app/blog/10-mobile-form-design-tips-for-better-ux +confidence: high +--- + +# Mobile Form Patterns — Формы и инпуты для мобильных + +## 1. Mobile Form Statistics + +- **63%** мобильных форм бросают на полпути (мобильная форма для завершения) +- Оптимизированные формы увеличивают completion rate до **43%** +- Мобильные формы имеют более высокий abandonment rate, чем десктоп +- Каждый дополнительный field = 10% снижение completion rate + +## 2. Input Types & Keyboards + +### 2.1 Correct Input Types + +```html + + + + + + + + + + + + + + + + + + + + +``` + +### 2.2 inputmode for Custom Keyboards + +```html + + + + + + + + + + + + + + +``` + +### 2.3 Keyboard Hints + +```html + + + + + + + + + + +``` + +## 3. Form Layout Patterns + +### 3.1 Single-Column Form + +```css +.mobile-form { + display: flex; + flex-direction: column; + gap: 20px; + padding: 20px 16px; +} + +.form-group { + display: flex; + flex-direction: column; + gap: 8px; +} +``` + +### 3.2 Floating Label Pattern + +```css +.form-group { + position: relative; +} + +.form-input { + min-height: 48px; + padding: 16px 12px 8px; + border: 1px solid var(--border-1); + border-radius: 8px; + background: var(--surface-1); + font-size: 16px; /* Prevents iOS zoom */ + width: 100%; + transition: border-color 0.15s, box-shadow 0.15s; +} + +.form-input:focus { + border-color: var(--primary); + outline: none; + box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 20%, transparent); +} + +.form-label { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + color: var(--text-secondary); + pointer-events: none; + transition: all 0.15s ease; + font-size: 16px; +} + +.form-input:focus ~ .form-label, +.form-input:not(:placeholder-shown) ~ .form-label { + top: 8px; + font-size: 12px; + color: var(--primary); +} +``` + +### 3.3 Multi-Field Row (When Appropriate) + +```css +.form-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; +} + +.form-row-3 { + grid-template-columns: 1fr 1fr 1fr; +} +``` + +**Правила:** +- Только для связанных полей (имя/отчество, город/индекс) +- Всегда single-column на экранах < 360px +- Never for unrelated fields + +## 4. Validation Patterns + +### 4.1 Real-Time Validation + +```css +.form-input.valid { + border-color: #22c55e; +} + +.form-input.invalid { + border-color: #ef4444; + animation: shake 0.3s ease; +} + +.form-error { + font-size: 12px; + color: #ef4444; + margin-top: 4px; + display: flex; + align-items: center; + gap: 4px; +} + +.form-success { + font-size: 12px; + color: #22c55e; + margin-top: 4px; +} + +@keyframes shake { + 0%, 100% { transform: translateX(0); } + 25% { transform: translateX(-4px); } + 75% { transform: translateX(4px); } +} +``` + +### 4.2 Inline Validation on Blur + +```javascript +// Validate on blur, not on every keystroke +input.addEventListener('blur', () => { + const value = input.value; + if (!validate(value)) { + input.classList.add('invalid'); + showError(input, getMessage(value)); + } else { + input.classList.remove('invalid'); + input.classList.add('valid'); + showSuccess(input); + } +}); +``` + +### 4.3 Validation Error Summary + +```html + + +``` + +## 5. Form UX Patterns + +### 5.1 Progressive Disclosure (Step by Step) + +```css +.form-step { + display: none; +} + +.form-step.active { + display: block; + animation: fadeIn 0.3s ease; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +.form-progress { + display: flex; + justify-content: space-between; + padding: 16px; + margin-bottom: 16px; +} + +.form-progress__step { + flex: 1; + height: 4px; + background: var(--surface-2); + margin: 0 4px; + border-radius: 2px; +} + +.form-progress__step.active { + background: var(--primary); +} +``` + +### 5.2 Autocomplete & Autofill + +```html + + + + + + +``` + +### 5.3 Masked Inputs + +```javascript +// Phone mask +function formatPhone(value) { + const cleaned = value.replace(/\D/g, ''); + const match = cleaned.match(/^(\d)(\d{0,3})(\d{0,3})(\d{0,2})(\d{0,2})$/); + if (match) { + return '+7' + (match[2] ? ' (' + match[2] : '') + + (match[3] ? ') ' + match[3] : '') + + (match[4] ? '-' + match[4] : '') + + (match[5] ? '-' + match[5] : ''); + } + return value; +} +``` + +## 6. Form Component Patterns + +### 6.1 Select/Picker + +```html + + + + +
+ Выберите дату + ▼ +
+
+
+
+ +
+
+
+``` + +### 6.2 Checkbox & Radio + +```css +/* Large touch targets */ +.form-checkbox, +.form-radio { + width: 44px; + height: 44px; + display: flex; + align-items: center; + justify-content: center; + border: 2px solid var(--border-1); + border-radius: 8px; + margin-right: 12px; + flex-shrink: 0; +} + +.form-checkbox::after, +.form-radio::after { + content: ''; + width: 16px; + height: 16px; + border-radius: 4px; + background: var(--primary); + opacity: 0; + transition: opacity 0.2s; +} + +.form-checkbox.checked::after, +.form-radio.checked::after { + opacity: 1; +} +``` + +### 6.3 Toggle Switch + +```css +.toggle { + position: relative; + width: 52px; + height: 32px; + border-radius: 16px; + background: var(--surface-2); + cursor: pointer; + transition: background 0.2s; +} + +.toggle::after { + content: ''; + position: absolute; + top: 2px; + left: 2px; + width: 28px; + height: 28px; + border-radius: 50%; + background: white; + box-shadow: 0 1px 3px rgba(0,0,0,0.2); + transition: transform 0.2s; +} + +.toggle.active { + background: var(--primary); +} + +.toggle.active::after { + transform: translateX(20px); +} +``` + +## 7. Form Anti-Patterns + +| ❌ Плохо | ✅ Хорошо | +|----------|-----------| +| Автофокус на первом поле | Автофокус только на 1 поле за шаг | +| Валидация при вводе | Валидация при blur или submit | +| Email type для всех текстовых полей | Правильный inputtype + inputmode | +| Фиксированная клавиатура | Динамическая клавиатура | +| 10+ полей в одну страницу | Прогрессивная подача (step-by-step) | +| Нет подсказок для форматов | Placeholder + подсказки | +| Только красная рамка при ошибке | Иконка + текст + анимация | +| Сложные пароли без указаний | Показ требований к паролю | + +## 8. Mobile Form Checklist + +| Category | Check | +|----------|-------| +| **Input Type** | Правильный type + inputmode | +| **Keyboard** | Правильная клавиатура для каждого поля | +| **Touch Targets** | Минимум 44×44px | +| **Labels** | Видимые labels (floating или above) | +| **Validation** | Inline, после blur, summary at top | +| **Autocomplete** | autocomplete attributes set | +| **Progress** | Step indicator для многостраничных | +| **Submit** | CTA всегда виден | +| **Feedback** | Success/error states | +| **Zoom** | 16px font-size на iOS | diff --git a/mobile/mobile-interaction-patterns.md b/mobile/mobile-interaction-patterns.md new file mode 100644 index 0000000..b7838f8 --- /dev/null +++ b/mobile/mobile-interaction-patterns.md @@ -0,0 +1,704 @@ +--- +title: Mobile Interaction Patterns +created: 2026-07-03 +updated: 2026-07-03 +type: system +tags: [mobile, interaction, gesture, touch, swipe, haptics, animation] +sources: + - https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions + - https://developer.mozilla.org/en-US/docs/Web/API/Touch_events + - https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events + - https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API + - https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations/Using_CSS_animations +confidence: high +--- + +# Mobile Interaction Patterns — Жесты, тач, анимации + +## 1. Touch Events & Gestures + +### 1.1 Basic Touch Handling + +```javascript +// Touch start — feedback at press +element.addEventListener('touchstart', (e) => { + const touch = e.touches[0]; + // Handle initial touch +}, { passive: true }); + +// Touch move — dragging +element.addEventListener('touchmove', (e) => { + e.preventDefault(); // Prevent scroll during drag +}, { passive: false }); + +// Touch end — release +element.addEventListener('touchend', (e) => { + // Handle release +}); +``` + +### 1.2 Swipe Detection + +```javascript +class SwipeHandler { + constructor(element, options = {}) { + this.element = element; + this.threshold = options.threshold || 50; + this.velocityThreshold = options.velocityThreshold || 0.3; + this.direction = options.direction || 'horizontal'; + this.onSwipe = options.onSwipe; + + this.startX = 0; + this.startY = 0; + this.startTime = 0; + this.isSwiping = false; + + this.bindEvents(); + } + + bindEvents() { + this.element.addEventListener('touchstart', (e) => { + this.startX = e.touches[0].clientX; + this.startY = e.touches[0].clientY; + this.startTime = Date.now(); + this.isSwiping = true; + }, { passive: true }); + + this.element.addEventListener('touchend', (e) => { + if (!this.isSwiping) return; + this.isSwiping = false; + + const endX = e.changedTouches[0].clientX; + const endY = e.changedTouches[0].clientY; + const endTime = Date.now(); + + const diffX = this.startX - endX; + const diffY = this.startY - endY; + const timeDiff = endTime - this.startTime; + const velocity = Math.abs(diffX) / timeDiff; + + // Check direction and distance + if (this.direction === 'horizontal' && Math.abs(diffX) > this.threshold) { + if (velocity > this.velocityThreshold) { + const direction = diffX > 0 ? 'left' : 'right'; + this.onSwipe?.(direction, diffX, diffY); + } + } else if (this.direction === 'vertical' && Math.abs(diffY) > this.threshold) { + if (velocity > this.velocityThreshold) { + const direction = diffY > 0 ? 'up' : 'down'; + this.onSwipe?.(direction, diffX, diffY); + } + } + }, { passive: true }); + } +} + +// Usage +new SwipeHandler(container, { + threshold: 50, + velocityThreshold: 0.3, + onSwipe: (direction, dx, dy) => { + console.log(`Swiped ${direction} (${dx}, ${dy})`); + } +}); +``` + +### 1.3 Long Press / Double Tap + +```javascript +class LongPressHandler { + constructor(element, onLongPress, onDoubleTap) { + this.element = element; + this.longPressThreshold = 500; + this.doubleTapThreshold = 300; + this.timeout = null; + this.tapCount = 0; + this.tapTimer = null; + + this.bindEvents(); + } + + bindEvents() { + this.element.addEventListener('touchstart', (e) => { + // Long press + this.timeout = setTimeout(() => { + this.longPress?.(e.touches[0]); + }, this.longPressThreshold); + + // Double tap + this.tapCount++; + clearTimeout(this.tapTimer); + this.tapTimer = setTimeout(() => { + if (this.tapCount === 2) { + this.doubleTap?.(e.changedTouches[0]); + } + this.tapCount = 0; + }, this.doubleTapThreshold); + }); + + this.element.addEventListener('touchend', () => { + clearTimeout(this.timeout); + }); + } +} +``` + +## 2. Pull to Refresh + +### 2.1 CSS + JS Pull to Refresh + +```html +
+
+
+
+
Обновление...
+
+
+ +
+``` + +```css +.pull-to-refresh { + position: fixed; + top: 0; + left: 0; + right: 0; + height: 60px; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + transform: translateY(-100%); + transition: transform 0.3s ease; + z-index: 100; +} + +.pull-to-refresh.active { + transform: translateY(0); +} + +.ptr-spinner { + width: 24px; + height: 24px; + border: 3px solid var(--border-1); + border-top-color: var(--primary); + border-radius: 50%; + animation: spin 0.6s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.ptr-label { + font-size: var(--text-sm); + color: var(--text-secondary); +} + +/* Scroll container */ +.scroll-container { + padding-top: 60px; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} +``` + +```javascript +class PullToRefresh { + constructor(element, onRefresh) { + this.element = element; + this.content = element.querySelector('.scroll-container'); + this.refresh = element.querySelector('.pull-to-refresh'); + this.onRefresh = onRefresh; + this.startY = 0; + this.currentY = 0; + this.threshold = 80; + + this.bindEvents(); + } + + bindEvents() { + this.content.addEventListener('touchstart', (e) => { + if (this.content.scrollTop === 0) { + this.startY = e.touches[0].clientY; + } + }, { passive: true }); + + this.content.addEventListener('touchmove', (e) => { + if (this.content.scrollTop === 0) { + this.currentY = e.touches[0].clientY; + const diff = this.currentY - this.startY; + + if (diff > 0) { + e.preventDefault(); + this.refresh.style.transform = `translateY(${Math.min(diff * 0.5, this.threshold)}px)`; + + if (diff >= this.threshold) { + this.refresh.classList.add('active'); + } + } + } + }, { passive: false }); + + this.content.addEventListener('touchend', () => { + if (this.refresh.classList.contains('active')) { + this.refresh.style.transform = 'translateY(0)'; + this.refresh.classList.add('active'); + this.onRefresh?.(() => { + this.refresh.classList.remove('active'); + }); + } + }); + } +} + +// Usage +new PullToRefresh(document.body, (done) => { + fetch('/api/data') + .then(r => r.json()) + .then(data => { + updateContent(data); + done(); + }); +}); +``` + +## 3. Touch Feedback Patterns + +### 3.1 Active State (Ripple-like) + +```css +.touch-feedback { + position: relative; + overflow: hidden; + transition: transform 0.1s ease, opacity 0.1s ease; + -webkit-tap-highlight-color: transparent; +} + +.touch-feedback:active { + transform: scale(0.97); + opacity: 0.8; +} + +/* Subtle press effect */ +.touch-feedback-press { + transition: transform 0.15s ease, box-shadow 0.15s ease; +} + +.touch-feedback-press:active { + transform: scale(0.98); + box-shadow: 0 0 0 2px var(--primary); +} +``` + +### 3.2 Haptic Feedback + +```javascript +// Web Vibration API (Android + some iOS) +function vibrate(pattern = [50]) { + if (navigator.vibrate) { + navigator.vibrate(pattern); + } +} + +// Haptic patterns for different actions +const haptics = { + light: [16], + medium: [30], + heavy: [50], + success: [30, 50, 30], + error: [100, 50, 100], + selection: [10], + click: [15] +}; + +// Usage +function handleTouch(element) { + element.addEventListener('touchend', () => { + vibrate(haptics.click); + }); +} +``` + +## 4. Scroll-Linked Animations + +### 4.1 Scroll Reveal + +```javascript +const revealObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('revealed'); + revealObserver.unobserve(entry.target); + } + }); +}, { + threshold: 0.1, + rootMargin: '0px 0px -50px 0px' +}); + +document.querySelectorAll('.reveal').forEach(el => { + revealObserver.observe(el); +}); +``` + +```css +.reveal { + opacity: 0; + transform: translateY(30px); + transition: opacity 0.6s ease, transform 0.6s ease; +} + +.reveal.revealed { + opacity: 1; + transform: translateY(0); +} + +/* Staggered reveal for lists */ +.reveal:nth-child(1) { transition-delay: 0ms; } +.reveal:nth-child(2) { transition-delay: 50ms; } +.reveal:nth-child(3) { transition-delay: 100ms; } +.reveal:nth-child(4) { transition-delay: 150ms; } +.reveal:nth-child(5) { transition-delay: 200ms; } +``` + +### 4.2 Parallax Scroll + +```css +.parallax-section { + position: relative; + overflow: hidden; + height: 60vh; +} + +.parallax-bg { + position: absolute; + top: -20%; + left: 0; + width: 100%; + height: 140%; + background-size: cover; + will-change: transform; +} + +.parallax-content { + position: relative; + z-index: 2; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} +``` + +```javascript +function handleParallax() { + const sections = document.querySelectorAll('.parallax-section'); + const scrollY = window.scrollY; + + sections.forEach(section => { + const rect = section.getBoundingClientRect(); + const offset = rect.top * 0.3; + const bg = section.querySelector('.parallax-bg'); + if (bg) { + bg.style.transform = `translateY(${offset}px)`; + } + }); +} + +window.addEventListener('scroll', handleParallax, { passive: true }); +``` + +### 4.3 Sticky Scroll Sections + +```css +.sticky-scroll-container { + display: flex; + height: 300vh; +} + +.sticky-panel { + position: sticky; + top: 0; + height: 100vh; + display: flex; + align-items: center; + justify-content: center; +} + +/* Sequential reveal */ +.sticky-panel:nth-child(1) { background: #f0f0f0; z-index: 1; } +.sticky-panel:nth-child(2) { background: #e0e0e0; z-index: 2; } +.sticky-panel:nth-child(3) { background: #d0d0d0; z-index: 3; } +``` + +## 5. Gesture Navigation + +### 5.1 Swipe Back + +```javascript +class SwipeBack { + constructor(element, onSwipeBack) { + this.element = element; + this.onSwipeBack = onSwipeBack; + this.startX = 0; + this.threshold = 100; + + this.bindEvents(); + } + + bindEvents() { + this.element.addEventListener('touchstart', (e) => { + this.startX = e.touches[0].clientX; + }, { passive: true }); + + this.element.addEventListener('touchmove', (e) => { + const diff = e.touches[0].clientX - this.startX; + if (diff > 0 && window.scrollX === 0) { + e.preventDefault(); + // Visual feedback + this.element.style.transform = `translateX(${diff}px)`; + this.element.style.opacity = 1 - (diff / this.threshold); + } + }, { passive: false }); + + this.element.addEventListener('touchend', (e) => { + const diff = e.changedTouches[0].clientX - this.startX; + if (diff > this.threshold) { + this.onSwipeBack?.(); + } + this.element.style.transform = ''; + this.element.style.opacity = ''; + }); + } +} +``` + +### 5.2 Horizontal Card Swipe + +```javascript +class HorizontalSwipe { + constructor(element, options = {}) { + this.element = element; + this.cards = element.querySelectorAll('.card'); + this.currentIndex = 0; + this.threshold = 100; + this.onSwipe = options.onSwipe; + + this.bindEvents(); + } + + bindEvents() { + this.cards.forEach(card => { + card.addEventListener('touchstart', (e) => { + this.startX = e.touches[0].clientX; + }, { passive: true }); + + card.addEventListener('touchend', (e) => { + const diff = e.changedTouches[0].clientX - this.startX; + if (Math.abs(diff) > this.threshold) { + this.currentIndex += diff > 0 ? 1 : -1; + this.currentIndex = Math.max(0, Math.min(this.currentIndex, this.cards.length - 1)); + this.onSwipe?.(this.currentIndex, diff > 0 ? 'next' : 'prev'); + } + }); + }); + } +} +``` + +## 6. Scroll-Linked CSS Animations + +### 6.1 Scroll-driven Animations (Modern) + +```css +@keyframes fade-in-up { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-on-scroll { + animation: fade-in-up linear; + animation-timeline: view(); + animation-range: entry 0% entry 25%; +} + +/* Progress bar that fills on scroll */ +.progress-track { + height: 4px; + background: var(--surface-2); + border-radius: 2px; + overflow: hidden; +} + +.progress-fill { + height: 100%; + background: var(--primary); + border-radius: 2px; + animation: progress linear; + animation-timeline: scroll(); + animation-range: 0 100vh; +} + +@keyframes progress { + to { width: 100%; } +} +``` + +### 6.2 Scroll-linked Typography + +```css +.scroll-text { + font-size: clamp(1rem, 1rem + 20cqw, 3rem); + transition: font-size 0.1s ease; +} + +/* Scale with scroll position */ +.scroll-scale { + transform: scale(calc(1 + scroll(target, transform) * 0.5)); + animation: scroll-scale linear; + animation-timeline: scroll(); +} +``` + +## 7. Touch-Optimized Carousel + +```javascript +class TouchCarousel { + constructor(element, options = {}) { + this.element = element; + this.slides = element.querySelectorAll('.slide'); + this.currentIndex = 0; + this.startX = 0; + this.currentX = 0; + this.isDragging = false; + this.threshold = 50; + this.autoplay = options.autoplay ?? true; + this.interval = options.interval ?? 5000; + this.timer = null; + + this.bindEvents(); + this.startAutoplay(); + } + + bindEvents() { + // Touch + this.element.addEventListener('touchstart', (e) => { + this.stopAutoplay(); + this.startX = e.touches[0].clientX; + this.isDragging = true; + this.element.style.transition = 'none'; + }, { passive: true }); + + this.element.addEventListener('touchmove', (e) => { + if (!this.isDragging) return; + this.currentX = e.touches[0].clientX; + const diff = this.currentX - this.startX; + this.slides[this.currentIndex].style.transform = `translateX(${diff}px)`; + }, { passive: true }); + + this.element.addEventListener('touchend', () => { + this.isDragging = false; + this.element.style.transition = 'transform 0.3s ease'; + const diff = this.currentX - this.startX; + if (Math.abs(diff) > this.threshold) { + this.goTo(diff > 0 ? this.currentIndex - 1 : this.currentIndex + 1); + } else { + this.goTo(this.currentIndex); + } + this.startAutoplay(); + }); + + // Indicator dots + this.dots = element.querySelectorAll('.dot'); + this.dots.forEach((dot, i) => { + dot.addEventListener('click', () => this.goTo(i)); + }); + } + + goTo(index) { + this.currentIndex = Math.max(0, Math.min(index, this.slides.length - 1)); + this.slides[this.currentIndex].style.transform = 'translateX(0)'; + this.updateDots(); + } + + updateDots() { + this.dots.forEach((dot, i) => { + dot.classList.toggle('active', i === this.currentIndex); + }); + } + + startAutoplay() { + this.timer = setInterval(() => { + this.goTo((this.currentIndex + 1) % this.slides.length); + }, this.interval); + } + + stopAutoplay() { + clearInterval(this.timer); + } +} +``` + +## 8. Performance Tips for Mobile Touch + +### 8.1 Passive Event Listeners + +```javascript +// Always use passive for scroll-related listeners +document.addEventListener('scroll', handler, { passive: true }); +document.addEventListener('touchstart', handler, { passive: true }); +``` + +### 8.2 Debounce Scroll + +```javascript +function debounceScroll(fn, delay = 16) { + let ticking = false; + return () => { + if (!ticking) { + requestAnimationFrame(() => { + fn(); + ticking = false; + }); + ticking = true; + } + }; +} + +window.addEventListener('scroll', debounceScroll(() => { + // Handle scroll +}, 16)); +``` + +### 8.3 GPU-Accelerated Animations + +```css +/* Always use transform + opacity for animations */ +.animated-element { + will-change: transform, opacity; + transform: translateZ(0); /* Force GPU layer */ +} + +/* Avoid animating: width, height, top, left, margin */ +``` + +## 9. Mobile Interaction Anti-Patterns + +| ❌ Плохо | ✅ Хорошо | +|----------|-----------| +| Hover-эффекты | Active/pressed states | +| Автоплей видео | Play по тапу пользователя | +| Скрытые жесты | Визуальные подсказки для жестов | +| 100vh секции | 100dvh (dynamic viewport) | +| Тяжелые scroll-анимации | CSS scroll-driven animations | +| Пасивные листенеры | Passive + debounce | +| Только визуальный фидбэк | Haptic + visual feedback | diff --git a/mobile/mobile-landing-patterns.md b/mobile/mobile-landing-patterns.md new file mode 100644 index 0000000..8297434 --- /dev/null +++ b/mobile/mobile-landing-patterns.md @@ -0,0 +1,923 @@ +--- +title: Mobile Landing Page Patterns +created: 2026-07-03 +updated: 2026-07-03 +type: system +tags: [mobile, landing, conversion, CTA, sections, mobile-first] +sources: + - https://www.webstacks.com/blog/mobile-website-design-best-practices + - https://www.browserstack.com/guide/how-to-implement-mobile-first-design + - https://www.engagecoders.com/responsive-web-design-mobile-first-development-best-practices-2025-guide/ +confidence: high +--- + +# Mobile Landing Page Patterns — Лендинги для мобильных + +## 1. Mobile Landing Page Structure + +### 1.1 Секвенция секций + +Оптимальный порядок для мобильных лендингов (сверху вниз): + +``` +1. Hero (с CTA) +2. Social Proof (логотипы клиентов) +3. Problem/Solution +4. Features/Benefits +5. How It Works +6. Testimonials +7. Pricing +8. FAQ +9. Final CTA +10. Footer +``` + +### 1.2 Hero Section (Mobile) + +```html +
+
+

Заголовок, который решает проблему

+

Подзаголовок в 1-2 предложения, объясняющий ценность

+ +
+
+ + + +
+ +
+
+
+``` + +```css +.mobile-hero { + padding: calc(24px + var(--safe-top)) 16px calc(var(--safe-bottom) + 24px); + display: flex; + flex-direction: column; + justify-content: center; + min-height: 80dvh; +} + +.hero-title { + font-size: clamp(2rem, 8vw, 3.5rem); + font-weight: 800; + line-height: 1.1; + margin-bottom: 16px; + letter-spacing: -0.03em; +} + +.hero-subtitle { + font-size: clamp(1.1rem, 3.5vw, 1.25rem); + line-height: 1.5; + color: var(--text-secondary); + margin-bottom: 24px; + max-width: 600px; +} + +.hero-cta-group { + display: flex; + flex-direction: column; + gap: 12px; + margin-bottom: 24px; +} + +.hero-social-proof { + display: flex; + align-items: center; + gap: 12px; +} + +.avatars { + display: flex; +} + +.avatar { + width: 32px; + height: 32px; + border-radius: 50%; + border: 2px solid var(--surface-1); + margin-left: -8px; +} + +.avatar:first-child { + margin-left: 0; +} + +.social-text { + font-size: var(--text-sm); + color: var(--text-secondary); +} +``` + +### 1.3 Social Proof Section + +```html +
+

Нам доверяют

+
+ + + + + +
+
+
+ 10K+ + Клиентов +
+
+ 99.9% + Uptime +
+
+ 4.9/5 + Рейтинг +
+
+
+``` + +```css +.social-proof { + padding: 48px 16px; + text-align: center; +} + +.section-label { + font-size: var(--text-sm); + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--text-secondary); + margin-bottom: 24px; +} + +.logo-grid { + display: flex; + justify-content: center; + align-items: center; + gap: 24px; + flex-wrap: wrap; + margin-bottom: 48px; +} + +.logo { + height: 24px; + opacity: 0.5; + filter: grayscale(1); + transition: opacity 0.2s, filter 0.2s; +} + +.logo:hover, +.logo:active { + opacity: 1; + filter: grayscale(0); +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; + padding: 24px; + background: var(--surface-2); + border-radius: 12px; +} + +.stat { + display: flex; + flex-direction: column; + gap: 4px; +} + +.stat-value { + font-size: var(--text-xl); + font-weight: 800; +} + +.stat-label { + font-size: var(--text-xs); + color: var(--text-secondary); +} +``` + +## 2. Feature/Benefit Sections + +### 2.1 Feature Card (Mobile) + +```html +
+

Всё что нужно для роста

+
+
+
📊
+

Аналитика

+

Мгновенные дашборды с ключевыми метриками

+
+
+
⚡
+

Скорость

+

Загрузка менее чем за 1 секунду

+
+
+
🔒
+

Безопасность

+

Шифрование данных и соответствие GDPR

+
+
+
+``` + +```css +.features { + padding: 48px 16px; +} + +.section-title { + font-size: clamp(1.5rem, 6vw, 2.5rem); + font-weight: 800; + text-align: center; + margin-bottom: 32px; + letter-spacing: -0.03em; +} + +.feature-list { + display: flex; + flex-direction: column; + gap: 16px; +} + +.feature-card { + display: flex; + gap: 16px; + padding: 20px; + background: var(--surface-1); + border: 1px solid var(--border-1); + border-radius: 12px; + transition: border-color 0.2s; +} + +.feature-card:active { + border-color: var(--primary); +} + +.feature-icon { + font-size: 2rem; + flex-shrink: 0; + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + background: var(--surface-2); + border-radius: 12px; +} + +.feature-title { + font-size: var(--text-lg); + font-weight: 700; + margin-bottom: 4px; +} + +.feature-desc { + font-size: var(--text-sm); + color: var(--text-secondary); + line-height: 1.5; +} +``` + +### 2.2 Feature Image + Text (Alternating) + +```html +
+
+

Быстрый старт за 2 минуты

+

Никаких сложных настроек. Подключите API и начните работу.

+ Документация → +
+
+ Dashboard preview +
+
+ +
+
+

Автоматизация процессов

+

Настройте один раз — система будет работать за вас.

+ Узнать больше → +
+
+ Automation preview +
+
+``` + +```css +.feature-showcase { + display: flex; + flex-direction: column; + padding: 48px 16px; + gap: 32px; +} + +.feature-showcase.reversed .feature-showcase__content { + order: 2; +} + +.feature-showcase.reversed .feature-showcase__media { + order: 1; +} + +.feature-showcase__title { + font-size: var(--text-xl); + font-weight: 700; + margin-bottom: 8px; +} + +.feature-showcase__desc { + font-size: var(--text-base); + color: var(--text-secondary); + line-height: 1.5; + margin-bottom: 16px; +} + +.feature-showcase__media { + width: 100%; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 4px 12px rgba(0,0,0,0.1); +} + +.feature-showcase__media img { + width: 100%; + aspect-ratio: 4/3; + object-fit: cover; +} + +@media (min-width: 768px) { + .feature-showcase { + flex-direction: row; + align-items: center; + gap: 48px; + padding: 64px 32px; + } + + .feature-showcase.reversed .feature-showcase__content { + order: 1; + } + + .feature-showcase.reversed .feature-showcase__media { + order: 2; + } + + .feature-showcase__content { + flex: 1; + } + + .feature-showcase__media { + flex: 1; + } +} +``` + +## 3. CTA Patterns for Mobile + +### 3.1 Inline CTA + +```html +
+
+

Готовы начать?

+

Присоединяйтесь к 10,000+ компаний, которые уже используют наш сервис

+
+ Создать аккаунт +
+``` + +```css +.cta-inline { + padding: 48px 16px; + text-align: center; + background: var(--surface-2); + border-radius: 16px; + margin: 0 16px 32px; +} + +.cta-inline__title { + font-size: clamp(1.25rem, 5vw, 1.75rem); + font-weight: 700; + margin-bottom: 8px; +} + +.cta-inline__desc { + font-size: var(--text-base); + color: var(--text-secondary); + margin-bottom: 24px; +} + +.btn-large { + width: 100%; + min-height: 56px; + font-size: var(--text-lg); +} +``` + +### 3.2 Sticky Bottom CTA + +```html +
+ + Начать бесплатно + + Без привязки карты +
+``` + +```css +.sticky-cta { + position: fixed; + bottom: 0; + left: 0; + right: 0; + padding: 12px 16px; + padding-bottom: calc(12px + var(--safe-bottom)); + background: var(--surface-1); + border-top: 1px solid var(--border-1); + z-index: 100; + display: flex; + flex-direction: column; + gap: 4px; + box-shadow: 0 -4px 12px rgba(0,0,0,0.08); +} + +.sticky-cta__btn { + width: 100%; + min-height: 56px; + text-align: center; + font-size: var(--text-lg); + font-weight: 700; + text-decoration: none; + display: flex; + align-items: center; + justify-content: center; +} + +.sticky-cta__hint { + text-align: center; + font-size: var(--text-xs); + color: var(--text-secondary); +} +``` + +### 3.3 Full-Bleed CTA + +```html +
+

Готовы трансформировать бизнес?

+

Запросите демо за 2 минуты

+ Запросить демо +
+``` + +```css +.cta-full-bleed { + padding: 64px 24px; + text-align: center; + background: var(--primary); + color: white; +} + +.cta-full-bleed__title { + font-size: clamp(1.5rem, 6vw, 2.5rem); + font-weight: 800; + margin-bottom: 12px; +} + +.cta-full-bleed__desc { + font-size: var(--text-lg); + opacity: 0.9; + margin-bottom: 32px; +} + +.btn-white { + background: white; + color: var(--primary); +} +``` + +## 4. Testimonials for Mobile + +```html +
+

Что говорят клиенты

+
+
+

"Этот сервис изменил наш подход к аналитике. Всё работает как часы."

+
+ Иван П. +
+

Иван Петров

+

CEO, TechCorp

+
+
+
+
+

"Наконец-то удобный интерфейс, который не ломается на телефоне."

+
+ Анна С. +
+

Анна Сидорова

+

CMO, Digital Agency

+
+
+
+
+
+``` + +```css +.testimonial-scroll { + display: flex; + gap: 16px; + overflow-x: auto; + scroll-snap-type: x mandatory; + -webkit-overflow-scrolling: touch; + padding: 0 16px 16px; + scrollbar-width: none; +} + +.testimonial-scroll::-webkit-scrollbar { + display: none; +} + +.testimonial-card { + flex: 0 0 auto; + width: min(340px, 85vw); + padding: 24px; + background: var(--surface-1); + border: 1px solid var(--border-1); + border-radius: 12px; + scroll-snap-align: center; +} + +.testimonial-text { + font-size: var(--text-base); + line-height: 1.6; + margin-bottom: 16px; + font-style: italic; +} + +.testimonial-author { + display: flex; + align-items: center; + gap: 12px; +} + +.testimonial-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + object-fit: cover; +} + +.testimonial-name { + font-weight: 600; + font-size: var(--text-sm); +} + +.testimonial-role { + font-size: var(--text-xs); + color: var(--text-secondary); +} +``` + +## 5. Pricing for Mobile + +```html +
+

Простые тарифы

+ +
+ Ежемесячно + + Ежегодно (-20%) +
+ +
+
+

Старт

+
+ ₽990 + + /мес +
+
    +
  • ✓ До 1,000 контактов
  • +
  • ✓ Базовая аналитика
  • +
  • ✓ Email поддержка
  • +
+ Начать +
+ + +
+
+``` + +```css +.pricing { + padding: 48px 16px; +} + +.pricing-toggle { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + margin-bottom: 32px; +} + +.switch { + width: 48px; + height: 28px; + position: relative; + cursor: pointer; +} + +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +.switch-slider { + position: absolute; + top: 0; left: 0; right: 0; bottom: 0; + background: var(--surface-2); + border-radius: 14px; + transition: background 0.2s; +} + +.switch-slider::after { + content: ''; + position: absolute; + left: 2px; + top: 2px; + width: 24px; + height: 24px; + border-radius: 50%; + background: white; + transition: transform 0.2s; +} + +.switch input:checked + .switch-slider { + background: var(--primary); +} + +.switch input:checked + .switch-slider::after { + transform: translateX(20px); +} + +.pricing-cards { + display: flex; + flex-direction: column; + gap: 16px; +} + +.pricing-card { + padding: 24px; + background: var(--surface-1); + border: 1px solid var(--border-1); + border-radius: 16px; +} + +.pricing-card.featured { + border-color: var(--primary); + box-shadow: 0 0 0 2px var(--primary); + position: relative; +} + +.pricing-card__badge { + position: absolute; + top: -12px; + right: 16px; + padding: 4px 12px; + background: var(--primary); + color: white; + font-size: var(--text-xs); + font-weight: 600; + border-radius: 12px; +} + +.pricing-card__price { + display: flex; + align-items: baseline; + gap: 4px; + margin-bottom: 16px; +} + +.price-monthly { + font-size: 2rem; + font-weight: 800; +} + +.price-yearly { + font-size: 2rem; + font-weight: 800; + color: #22c55e; +} + +.pricing-card__features { + list-style: none; + padding: 0; + margin-bottom: 24px; +} + +.pricing-card__features li { + padding: 8px 0; + border-bottom: 1px solid var(--border-1); + font-size: var(--text-sm); +} + +.pricing-card__features li:last-child { + border-bottom: none; +} + +.pricing-card__btn { + width: 100%; + min-height: 48px; +} +``` + +## 6. FAQ for Mobile + +```html +
+

Частые вопросы

+
+
+ + Как начать использовать сервис? + + + +
+

Регистрация занимает 2 минуты. Просто укажите email, создайте пароль и следуйте инструкциям на экране.

+
+
+
+ + Можно ли отменить подписку? + + + +
+

Да, вы можете отменить подписку в любой момент из настроек аккаунта. Никаких скрытых платежей.

+
+
+
+ + Есть ли бесплатный пробный период? + + + +
+

Да, 14 дней бесплатного использования всех функций. Без привязки карты.

+
+
+
+
+``` + +```css +.faq { + padding: 48px 16px; +} + +.accordion { + display: flex; + flex-direction: column; + gap: 8px; +} + +.accordion-item { + background: var(--surface-1); + border: 1px solid var(--border-1); + border-radius: 12px; + overflow: hidden; +} + +.accordion-item__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + cursor: pointer; + font-size: var(--text-base); + font-weight: 600; + list-style: none; + min-height: 56px; +} + +.accordion-item__header::-webkit-details-marker { + display: none; +} + +.accordion-icon { + font-size: 1.25rem; + transition: transform 0.2s; + flex-shrink: 0; +} + +.accordion-item[open] .accordion-icon { + transform: rotate(45deg); +} + +.accordion-item__content { + padding: 0 20px 16px; + font-size: var(--text-sm); + line-height: 1.5; + color: var(--text-secondary); +} +``` + +## 7. Footer for Mobile + +```html + +``` + +```css +.mobile-footer { + padding: 48px 16px 32px; + text-align: center; + background: var(--surface-2); +} + +.footer-links { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 16px; + margin-bottom: 24px; +} + +.footer-links a { + color: var(--text-secondary); + text-decoration: none; + font-size: var(--text-sm); + min-height: 36px; + display: flex; + align-items: center; +} + +.footer-copyright { + font-size: var(--text-xs); + color: var(--text-secondary); +} +``` + +## 8. Mobile Landing Checklist + +| Category | Check | +|----------|-------| +| **Hero** | Заголовок + подзаголовок + CTA + social proof | +| **CTA** | Минимум 2 CTA, sticky bottom CTA | +| **Features** | Карточки с иконками, скриншоты чередуются | +| **Social Proof** | Логотипы клиентов, статистика, отзывы | +| **Pricing** | Переключатель месяц/год, 2-3 тарифа | +| **FAQ** | Аккордеон, 4-6 вопросов | +| **Performance** | LCP < 2.5s, lazy loading, сжатие | +| **Navigation** | Sticky nav или bottom tab bar | +| **Footer** | Ссылки, копирайт | +| **Testing** | iPhone + Android, реальные устройства | diff --git a/mobile/mobile-layout-patterns.md b/mobile/mobile-layout-patterns.md new file mode 100644 index 0000000..9de6fcd --- /dev/null +++ b/mobile/mobile-layout-patterns.md @@ -0,0 +1,570 @@ +--- +title: Mobile Layout Systems +created: 2026-07-03 +updated: 2026-07-03 +type: system +tags: [mobile, layout, system, grid, container-queries, responsive] +sources: + - https://www.uxpin.com/studio/blog/best-practices-examples-of-excellent-responsive-design/ + - https://www.startdesigns.com/blog/responsive-web-design-2026/ + - https://www.jamesrossjr.com/blog/responsive-web-design-best-practices + - https://scrimba.com/articles/responsive-web-design-a-complete-guide-2026-2/ + - https://dev.to/gerryleonugroho/responsive-design-breakpoints-2025-playbook-53ih +confidence: high +--- + +# Mobile Layout Systems — Системы мобильной верстки + +## 1. Mobile Grid Architecture + +### Fluid Grid с Container Queries + +```css +/* Container query grid — компонент знает свой размер */ +.grid-container { + container-type: inline-size; + container-name: grid; +} + +@container grid (min-width: 300px) { + .grid { + display: grid; + grid-template-columns: 1fr; + gap: 16px; + } +} + +@container grid (min-width: 600px) { + .grid { + grid-template-columns: 1fr 1fr; + gap: 24px; + } +} + +@container grid (min-width: 900px) { + .grid { + grid-template-columns: repeat(3, 1fr); + gap: 32px; + } +} +``` + +### Mobile-First CSS Grid Pattern + +```css +.mobile-grid { + /* Mobile: single column */ + display: grid; + grid-template-columns: 1fr; + gap: 16px; + padding: 0 16px; +} + +/* Tablet landscape */ +@media (min-width: 768px) { + .mobile-grid { + grid-template-columns: repeat(2, 1fr); + gap: 24px; + padding: 0 24px; + } +} + +/* Desktop */ +@media (min-width: 1024px) { + .mobile-grid { + grid-template-columns: repeat(3, 1fr); + gap: 32px; + max-width: 1200px; + margin: 0 auto; + padding: 0 32px; + } +} +``` + +## 2. Mobile Layout Patterns + +### 2.1 Single-Column Stack (Mobile Base) + +Основной паттерн для мобильных — контент идет вертикально. + +```css +.mobile-stack { + display: flex; + flex-direction: column; + gap: 24px; + padding: 20px 16px 100px; /* 100px bottom для sticky CTA */ +} + +.mobile-stack .section { + width: 100%; +} +``` + +**Применение:** Главная страница лендинга, product pages, blog posts. + +### 2.2 Full-Bleed Hero (Mobile) + +На мобайле hero часто занимает весь экран. + +```css +.mobile-hero { + height: 100dvh; /* dynamic viewport height */ + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: center; + padding: 24px; + padding-bottom: calc(var(--safe-bottom) + 24px); + text-align: center; +} + +.mobile-hero__title { + font-size: clamp(2rem, 8vw, 4rem); + line-height: 1.1; + margin-bottom: 16px; +} + +.mobile-hero__subtitle { + font-size: clamp(1rem, 4vw, 1.25rem); + color: var(--text-secondary); + margin-bottom: 24px; + max-width: 600px; +} +``` + +### 2.3 Card Stack (Mobile Feed) + +Классический паттерн соцсетей и маркетплейсов. + +```css +.card-stack { + display: flex; + flex-direction: column; + gap: 16px; + padding: 16px; + padding-bottom: calc(var(--safe-bottom) + 80px); +} + +.card-stack .card { + background: var(--surface-1); + border-radius: 12px; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0,0,0,0.08); +} + +.card-stack .card__image { + width: 100%; + aspect-ratio: 16/9; + object-fit: cover; +} + +.card-stack .card__content { + padding: 16px; +} +``` + +### 2.4 Swipeable Carousel (Mobile) + +```css +.swipe-carousel { + display: flex; + gap: 16px; + overflow-x: auto; + scroll-snap-type: x mandatory; + scroll-behavior: smooth; + padding: 0 16px 16px; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; +} + +.swipe-carousel::-webkit-scrollbar { + display: none; +} + +.swipe-carousel .carousel-item { + scroll-snap-align: start; + flex: 0 0 auto; + width: min(280px, 85vw); +} + +/* Pagination dots */ +.carousel-dots { + display: flex; + justify-content: center; + gap: 8px; + padding: 16px; +} + +.carousel-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--text-secondary); + opacity: 0.4; + transition: opacity 0.2s, transform 0.2s; +} + +.carousel-dot.active { + opacity: 1; + transform: scale(1.3); + background: var(--primary); +} +``` + +### 2.5 Split Screen (Tablet/Desktop) + +```css +.split-screen { + display: flex; + flex-direction: column; +} + +.split-screen .split-content, +.split-screen .split-media { + flex: 1; + padding: 24px 16px; +} + +.split-screen .split-media { + order: -1; /* media first on mobile */ +} + +@media (min-width: 768px) { + .split-screen { + flex-direction: row; + } + + .split-screen .split-content, + .split-screen .split-media { + flex: 0 0 50%; + padding: 48px 32px; + } + + .split-screen .split-media { + order: 1; /* media right on desktop */ + } +} +``` + +### 2.6 Off-Canvas Navigation + +```css +.offcanvas-nav { + position: fixed; + top: 0; + left: 0; + width: 85vw; + max-width: 320px; + height: 100dvh; + background: var(--surface-1); + transform: translateX(-100%); + transition: transform 0.3s ease; + z-index: 1000; + padding: 24px; + padding-top: calc(24px + var(--safe-top)); +} + +.offcanvas-nav.open { + transform: translateX(0); +} + +.offcanvas-nav__overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.5); + opacity: 0; + visibility: hidden; + transition: opacity 0.3s ease; + z-index: 999; +} + +.offcanvas-nav.open + .offcanvas-nav__overlay { + opacity: 1; + visibility: visible; +} +``` + +### 2.7 Bottom Sheet Pattern + +```css +.bottom-sheet { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: var(--surface-1); + border-radius: 16px 16px 0 0; + transform: translateY(100%); + transition: transform 0.3s ease; + z-index: 1000; + padding: 16px; + padding-bottom: calc(16px + var(--safe-bottom)); + max-height: 80dvh; + overflow-y: auto; +} + +.bottom-sheet.open { + transform: translateY(0); +} + +.bottom-sheet__handle { + width: 40px; + height: 4px; + background: var(--border-1); + border-radius: 2px; + margin: 0 auto 16px; +} +``` + +### 2.8 Floating Action Button (FAB) + +```css +.fab { + position: fixed; + bottom: calc(var(--safe-bottom) + 24px); + right: 24px; + width: 56px; + height: 56px; + border-radius: 50%; + background: var(--primary); + color: white; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + z-index: 100; + border: none; + cursor: pointer; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.fab:active { + transform: scale(0.9); +} +``` + +## 3. Mobile Typography & Spacing System + +### Fluid Typography + +```css +:root { + /* Clamp-based fluid type scale */ + --text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem); + --text-sm: clamp(0.875rem, 0.8rem + 0.3vw, 1rem); + --text-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem); + --text-lg: clamp(1.125rem, 1rem + 0.6vw, 1.25rem); + --text-xl: clamp(1.25rem, 1.1rem + 0.75vw, 1.5rem); + --text-2xl: clamp(1.5rem, 1.2rem + 1.5vw, 2rem); + --text-3xl: clamp(1.875rem, 1.4rem + 2.3vw, 2.5rem); + --text-4xl: clamp(2.25rem, 1.6rem + 3.25vw, 3rem); +} +``` + +### Mobile Spacing Scale + +```css +:root { + --space-xs: 4px; + --space-sm: 8px; + --space-md: 16px; + --space-lg: 24px; + --space-xl: 32px; + --space-2xl: 48px; + --space-3xl: 64px; +} +``` + +## 4. Mobile Component Patterns + +### Mobile Navigation Bar (Bottom Tab) + +```css +.bottom-nav { + position: fixed; + bottom: 0; + left: 0; + right: 0; + height: calc(56px + var(--safe-bottom)); + padding-bottom: var(--safe-bottom); + background: var(--surface-1); + border-top: 1px solid var(--border-1); + display: flex; + justify-content: space-around; + align-items: center; + z-index: 100; +} + +.bottom-nav__item { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + padding: 8px 16px; + color: var(--text-secondary); + text-decoration: none; + font-size: 12px; + transition: color 0.2s; +} + +.bottom-nav__item.active { + color: var(--primary); +} + +.bottom-nav__icon { + width: 24px; + height: 24px; +} +``` + +### Mobile Card + +```css +.mobile-card { + background: var(--surface-1); + border-radius: 12px; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0,0,0,0.08); +} + +.mobile-card__image { + width: 100%; + aspect-ratio: 16/9; + object-fit: cover; +} + +.mobile-card__content { + padding: 16px; +} + +.mobile-card__title { + font-size: var(--text-lg); + font-weight: 600; + margin-bottom: 8px; +} + +.mobile-card__meta { + font-size: var(--text-sm); + color: var(--text-secondary); + margin-bottom: 12px; +} +``` + +### Mobile Button + +```css +.mobile-button { + min-height: 48px; + padding: 12px 24px; + border-radius: 8px; + font-size: var(--text-base); + font-weight: 600; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + border: none; + cursor: pointer; + transition: background 0.2s, transform 0.1s; + -webkit-tap-highlight-color: transparent; +} + +.mobile-button:active { + transform: scale(0.98); +} + +.mobile-button--primary { + background: var(--primary); + color: white; +} + +.mobile-button--secondary { + background: var(--surface-2); + color: var(--text-primary); +} + +.mobile-button--ghost { + background: transparent; + color: var(--primary); +} +``` + +## 5. Mobile Performance Optimization + +### Image Optimization Pattern + +```html + + + Description + +``` + +### CSS Optimization + +```css +/* Mobile-first critical CSS (inlined in ) */ +/* Target: < 14KB gzipped for mobile LCP */ + +@layer mobile, tablet, desktop; +``` + +### Lazy Loading Pattern + +```javascript +const lazyImages = document.querySelectorAll('.lazy-image'); +const lazyObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const img = entry.target; + img.src = img.dataset.src || img.src; + img.classList.add('loaded'); + lazyObserver.unobserve(img); + } + }); +}, { + rootMargin: '200px 0px', + threshold: 0.01 +}); + +lazyImages.forEach(img => lazyObserver.observe(img)); +``` + +## 6. Mobile Testing Checklist + +| Category | Check | Tool | +|----------|-------|------| +| **Layout** | Single-column flow at 320px | Chrome DevTools | +| **Touch** | All tap targets ≥ 44×44px | Lighthouse | +| **Typography** | No zoom on iOS input focus | Real device | +| **Safe Area** | No overlap with notch/home indicator | iPhone real device | +| **Performance** | LCP < 2.5s, INP < 200ms, CLS < 0.1 | PageSpeed Insights | +| **Scroll** | Horizontal scrolls fixed, vertical smooth | Real device | +| **Forms** | Correct keyboard types | Lighthouse | +| **Dark Mode** | All components readable | DevTools | +| **Orientation** | Portrait & landscape | Real device | + +## 7. Mobile Anti-Patterns + +| ❌ Плохо | ✅ Хорошо | +|----------|-----------| +| Hamburger-меню для основных навигаций | Bottom tab bar для 3-5 разделов | +| Hover-эффекты | Active/pressed states | +| Фиксированные ширины | Fluid + container queries | +| Полноценные изображения | Responsive images + lazy loading | +| Popup-формы на мобайле | Inline-формы | +| 100vh для full-height | 100dvh | +| Скрытые CTA | Sticky bottom CTA | +| Автоплей видео | Play/pause по тапу | diff --git a/mobile/mobile-navigation-patterns.md b/mobile/mobile-navigation-patterns.md new file mode 100644 index 0000000..225f32b --- /dev/null +++ b/mobile/mobile-navigation-patterns.md @@ -0,0 +1,436 @@ +--- +title: Mobile Navigation Patterns +created: 2026-07-03 +updated: 2026-07-03 +type: system +tags: [mobile, navigation, ux, bottom-nav, hamburger, gesture] +sources: + - https://www.uxpin.com/studio/blog/mobile-navigation-examples/ + - https://www.uxpin.com/studio/blog/mobile-navigation-patterns-pros-and-cons/ + - https://www.seven-squar-tech.com/which-mobile-navigation-patterns-works-what-fails/ + - https://claritee.io/blog/creative-mobile-ui-patterns-for-enhanced-navigation/ +confidence: high +--- + +# Mobile Navigation Patterns — Мобильная навигация + +## 1. Типы навигации для мобильных + +### 1.1 Bottom Tab Bar (Наиболее предпочтительная) + +```css +.bottom-nav { + position: fixed; + bottom: 0; + left: 0; + right: 0; + height: calc(56px + var(--safe-bottom)); + padding-bottom: var(--safe-bottom); + background: var(--surface-1); + border-top: 1px solid var(--border-1); + display: flex; + justify-content: space-around; + align-items: center; + z-index: 100; + box-shadow: 0 -2px 10px rgba(0,0,0,0.05); +} + +.bottom-nav__item { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + padding: 8px 16px; + color: var(--text-secondary); + text-decoration: none; + font-size: 12px; + transition: color 0.2s; + min-width: 48px; + min-height: 48px; + justify-content: center; +} + +.bottom-nav__item.active { + color: var(--primary); +} + +.bottom-nav__item.active .bottom-nav__icon { + transform: scale(1.1); +} + +.bottom-nav__icon { + width: 24px; + height: 24px; + transition: transform 0.2s; +} +``` + +**Правила:** +- 3-5 элементов максимум +- Иконка + подпись +- Текущий раздел выделен +- Не скрывать при скролле +- Лучше чем hamburger (все элементы видны) + +### 1.2 Top Navigation Bar + +```css +.top-nav { + position: sticky; + top: 0; + left: 0; + right: 0; + height: 56px; + padding: 0 16px; + background: var(--surface-1); + border-bottom: 1px solid var(--border-1); + display: flex; + align-items: center; + justify-content: space-between; + z-index: 100; +} + +.top-nav__title { + font-size: var(--text-lg); + font-weight: 600; +} + +.top-nav__actions { + display: flex; + gap: 16px; + align-items: center; +} + +.top-nav__action { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background: transparent; + border: none; + color: var(--text-primary); + cursor: pointer; + transition: background 0.2s; +} + +.top-nav__action:active { + background: var(--surface-2); +} +``` + +**Правила:** +- Sticky при скролле (не fixed) +- Логотип слева, действия справа +- Заголовок по центру +- Минимум 2-3 действия + +### 1.3 Hamburger Menu (Slide-in Drawer) + +```css +.hamburger-menu { + position: fixed; + top: 0; + left: 0; + width: 85vw; + max-width: 320px; + height: 100dvh; + background: var(--surface-1); + transform: translateX(-100%); + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + z-index: 1000; + padding: 24px; + padding-top: calc(24px + var(--safe-top)); + overflow-y: auto; +} + +.hamburger-menu.open { + transform: translateX(0); +} + +.hamburger-menu__overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.5); + opacity: 0; + visibility: hidden; + transition: opacity 0.3s ease; + z-index: 999; +} + +.hamburger-menu.open + .hamburger-menu__overlay { + opacity: 1; + visibility: visible; +} + +.hamburger-menu__link { + display: flex; + align-items: center; + gap: 12px; + padding: 16px; + color: var(--text-primary); + text-decoration: none; + font-size: var(--text-base); + border-radius: 8px; + transition: background 0.2s; +} + +.hamburger-menu__link:active { + background: var(--surface-2); +} + +.hamburger-menu__link .menu-icon { + width: 24px; + height: 24px; +} +``` + +**Правила:** +- 5-8 пунктов максимум +- Иконка + текст +- Разделители между секциями +- Закрывать по тапу на overlay или свайпу +- Всегда видный close button + +### 1.4 Bottom Sheet Navigation + +```css +.bottom-sheet-nav { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: var(--surface-1); + border-radius: 16px 16px 0 0; + transform: translateY(100%); + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + z-index: 1000; + padding: 16px; + padding-bottom: calc(16px + var(--safe-bottom)); + max-height: 70dvh; + overflow-y: auto; +} + +.bottom-sheet-nav.open { + transform: translateY(0); +} + +.bottom-sheet-nav__handle { + width: 40px; + height: 4px; + background: var(--border-1); + border-radius: 2px; + margin: 0 auto 16px; + cursor: grab; +} + +.bottom-sheet-nav__grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; +} + +.bottom-sheet-nav__item { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 16px; + border-radius: 12px; + text-decoration: none; + color: var(--text-primary); +} + +.bottom-sheet-nav__item:active { + background: var(--surface-2); +} + +.bottom-sheet-nav__item .nav-icon { + width: 32px; + height: 32px; + background: var(--surface-2); + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; +} +``` + +### 1.5 Pill Navigation (Chip Scroller) + +```css +.pill-nav { + display: flex; + gap: 8px; + overflow-x: auto; + padding: 12px 16px; + scrollbar-width: none; + -webkit-overflow-scrolling: touch; +} + +.pill-nav::-webkit-scrollbar { + display: none; +} + +.pill-nav__item { + padding: 8px 16px; + border-radius: 24px; + background: var(--surface-2); + color: var(--text-secondary); + white-space: nowrap; + font-size: var(--text-sm); + border: 1px solid var(--border-1); + text-decoration: none; + transition: all 0.2s; + min-height: 36px; + display: flex; + align-items: center; +} + +.pill-nav__item.active { + background: var(--primary); + color: white; + border-color: var(--primary); +} + +.pill-nav__item:active { + transform: scale(0.95); +} +``` + +**Где использовать:** Категории, фильтры, быстрые действия. + +## 2. Navigation Comparison Matrix + +| Паттерн | Кол-во пунктов | Видимость | Доступность | Скорость | +|---------|---------------|-----------|-------------|----------| +| Bottom Tab | 3-5 | ★★★★★ | ★★★★★ | ★★★★★ | +| Top Nav | 2-3 | ★★★★☆ | ★★★★☆ | ★★★★☆ | +| Hamburger | 5-8 | ★☆☆☆☆ | ★★★☆☆ | ★★☆☆☆ | +| Bottom Sheet | 6-12 | ★☆☆☆☆ | ★★★☆☆ | ★★★☆☆ | +| Pill Nav | 5-15 | ★★★★☆ | ★★★★☆ | ★★★★☆ | + +## 3. Navigation Rules + +### Golden Rules + +1. **3-5 основных разделов** — больше требует hierarchy +2. **Bottom Tab > Hamburger** — видимая навигация всегда лучше +3. **Иконка + текст** — всегда, никогда только иконки +4. **Текущий раздел всегда выделен** — не заставляйте пользователя думать +5. **Навигация не исчезает при скролле** — sticky или fixed +6. **Один паттерн на сайт** — не смешивайте bottom tab и hamburger + +### When to Use What + +| Ситуация | Паттерн | +|----------|---------| +| 3-5 основных разделов | Bottom Tab Bar | +| Много разделов (>5) | Bottom Tab + More (bottom sheet) | +| Product page (1 раздел) | Top Nav | +| Landing page (1 страница) | Нет навигации или якоря | +| Admin panel | Side drawer (left) | +| E-commerce categories | Pill Nav | + +## 4. Navigation Patterns for Content + +### 4.1 Swipe to Navigate + +```css +.swipe-container { + display: flex; + overflow-x: hidden; + scroll-snap-type: x mandatory; + scroll-behavior: smooth; + -webkit-overflow-scrolling: touch; +} + +.swipe-container .page { + flex: 0 0 100%; + scroll-snap-align: start; +} +``` + +### 4.2 Breadcrumbs (Mobile) + +```css +.breadcrumbs { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 16px; + overflow-x: auto; + scrollbar-width: none; + white-space: nowrap; + font-size: var(--text-sm); +} + +.breadcrumbs::-webkit-scrollbar { + display: none; +} + +.breadcrumbs__item { + color: var(--text-secondary); + text-decoration: none; +} + +.breadcrumbs__item.active { + color: var(--text-primary); + font-weight: 500; +} + +.breadcrumbs__separator { + color: var(--text-secondary); + font-size: 12px; +} +``` + +### 4.3 Tab Content Navigation + +```css +.tab-content { + position: relative; +} + +.tab-content__header { + position: sticky; + top: 56px; + background: var(--surface-1); + border-bottom: 1px solid var(--border-1); + z-index: 50; +} + +.tab-content__body { + padding: 16px; +} + +.tab-content__section { + margin-bottom: 32px; +} + +.tab-content__section-title { + font-size: var(--text-xl); + font-weight: 700; + margin-bottom: 16px; + position: sticky; + top: 100px; + background: var(--surface-1); + padding: 8px 0; + z-index: 10; +} +``` + +## 5. Navigation Anti-Patterns + +| ❌ Плохо | ✅ Хорошо | +|----------|-----------| +| Hamburger для 3-4 разделов | Bottom Tab Bar | +| Скрытие навигации при скролле | Sticky/Fixed навигация | +| Только иконки без текста | Иконка + подпись | +| 6+ пунктов в bottom nav | Разделить на разделы | +| Навигация на top для основного | Bottom для основных | +| Нет выделенного активного состояния | Четкий active state | diff --git a/mobile/mobile-patterns.md b/mobile/mobile-patterns.md new file mode 100644 index 0000000..2888098 --- /dev/null +++ b/mobile/mobile-patterns.md @@ -0,0 +1,663 @@ +--- +title: Mobile Layout & Pattern Systems +created: 2026-07-03 +updated: 2026-07-03 +type: system +tags: [mobile, layout, patterns, responsive, touch, mobile-first] +sources: + - https://www.uxpin.com/studio/blog/mobile-navigation-examples/ + - https://www.uxpin.com/studio/blog/mobile-navigation-patterns-pros-and-cons/ + - https://www.letsgroto.com/blog/mobile-first-responsive-design-best-practices + - https://www.uxpin.com/studio/blog/best-practices-examples-of-excellent-responsive-design/ + - https://www.startdesigns.com/blog/responsive-web-design-2026/ + - https://www.jamesrossjr.com/blog/responsive-web-design-best-practices + - https://dev.to/gerryleonugroho/responsive-design-breakpoints-2025-playbook-53ih + - https://scrimba.com/articles/responsive-web-design-a-complete-guide-2026-2/ +confidence: high +--- + +# Mobile Layout & Pattern Systems — Мобильные паттерны верстки + +## 1. Базовые принципы + +### Mobile-First: философия, а не техника + +Мобильный дизайн — это дисциплина приоритизации. На экране 360×800px каждое пиксельное решение имеет вес. + +| Принцип | Описание | +|---------|----------| +| **Progressive Enhancement** | Базовый контент → улучшенные компоненты → desktop-роскошь | +| **One-thumb Zone** | Основные интерактивные элементы в нижней половине экрана | +| **Content-First Flow** | Сначала контент, потом декор (всегда) | +| **Touch-Optimized** | 44×44px минимальный tap target, 8px gap между touch-элементами | +| **Performance-Aware** | Каждый миллисекунда LCP на мобильном важнее десктопа | + +### Глобальные CSS Variables для Mobile + +```css +:root { + /* Safe Area — iOS notch, Android gesture bar */ + --safe-top: env(safe-area-inset-top, 20px); + --safe-bottom: env(safe-area-inset-bottom, 34px); + --safe-left: env(safe-area-inset-left, 0px); + --safe-right: env(safe-area-inset-right, 0px); + + /* Typography — fluid on mobile */ + --text-xs: clamp(0.75rem, 2vw, 0.875rem); + --text-sm: clamp(0.875rem, 2.5vw, 1rem); + --text-base: clamp(1rem, 3vw, 1.125rem); + --text-lg: clamp(1.125rem, 3.5vw, 1.25rem); + --text-xl: clamp(1.25rem, 4vw, 1.5rem); + + /* Spacing — mobile-optimized */ + --space-xs: 0.25rem; + --space-sm: 0.5rem; + --space-md: 1rem; + --space-lg: 1.5rem; + --space-xl: 2rem; + --space-2xl: 3rem; + + /* Touch targets */ + --touch-min: 44px; + --touch-gap: 8px; + + /* Breakpoints — content-based, not device-based */ + --bp-sm: 480px; /* Large phones */ + --bp-md: 768px; /* Tablets portrait */ + --bp-lg: 1024px; /* Tablets landscape */ + --bp-xl: 1280px; /* Desktops */ +} +``` + +## 2. Breakpoints: Content-Based, Not Device-Based + +### Современная система breakpoints + +```css +/* Content-first breakpoints */ +@media (min-width: 640px) { /* sm: Large phones, small tablets */ } +@media (min-width: 768px) { /* md: Tablets */ } +@media (min-width: 1024px) { /* lg: Small laptops */ } +@media (min-width: 1280px) { /* xl: Desktops */ } +@media (min-width: 1536px) { /* 2xl: Large desktops */ } + +/* Container Queries — component-level адаптивность */ +.card-container { + container-type: inline-size; + container-name: card; +} + +@container card (min-width: 400px) { + .card { + grid-template-columns: 1fr 2fr; + } +} + +@container card (min-width: 600px) { + .card { + grid-template-columns: 1fr 1fr 1fr; + } +} +``` + +### Почему container queries > media queries + +| Media Queries | Container Queries | +|---------------|-------------------| +| Адаптация по viewport | Адаптация по родительскому контейнеру | +| Один layout для всей страницы | Разные layout для разных компонентов | +| Медленные перерасчеты | Реакция на изменение контейнера | +| Не работает в iframe/grid | Работает везде | + +## 3. Mobile Layout Patterns + +### 3.1 Single-Column Stack (Базовый) + +Все элементы вертикально, 16-24px gap между секциями. + +```css +.mobile-stack { + display: flex; + flex-direction: column; + gap: var(--space-lg); + padding: var(--space-lg) var(--space-md) calc(var(--safe-bottom) + var(--space-lg)) var(--space-md); +} + +.mobile-stack > * { + width: 100%; + max-width: 100%; +} +``` + +**Где использовать:** Лендинги, product pages, статьи, landing pages для мобайла. + +### 3.2 Full-Bleed Sections + +Секции, выходящие за границы контейнера, создают глубину. + +```css +.full-bleed-section { + margin-left: calc(var(--space-md) * -1); + margin-right: calc(var(--space-md) * -1); +} + +.full-bleed-section .container { + padding-left: var(--space-lg); + padding-right: var(--space-lg); +} +``` + +**Где использовать:** Hero, testimonials, image-heavy sections. + +### 3.3 Bento Grid (Mobile Adaptation) + +На мобайле bento grid превращается в single-column с карточками разной высоты. + +```css +.bento-grid { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-md); +} + +.bento-card { + min-height: var(--touch-min); + padding: var(--space-lg); + border-radius: var(--space-md); +} + +.bento-card.featured { + order: -1; /* featured card first */ +} +``` + +### 3.4 Sticky Bottom CTA + +Кнопка действия всегда видна внизу экрана, над клавиатурой. + +```css +.sticky-bottom-cta { + position: fixed; + bottom: 0; + left: 0; + right: 0; + padding: var(--space-md) var(--space-lg) calc(var(--safe-bottom) + var(--space-md)); + background: linear-gradient(to top, rgba(0,0,0,0.9), transparent); + z-index: 100; +} + +.sticky-bottom-cta button { + min-height: var(--touch-min); + width: 100%; + font-size: var(--text-lg); + font-weight: 600; +} +``` + +**Где использовать:** E-commerce, формы, booking-системы. + +### 3.5 Card-First Layout + +Контент карточками — основной паттерн мобайла. + +```css +.card-list { + display: flex; + flex-direction: column; + gap: var(--space-md); + padding: var(--space-md) var(--space-lg); +} + +.card { + background: var(--surface-1); + border-radius: var(--space-md); + padding: var(--space-lg); + box-shadow: 0 1px 3px rgba(0,0,0,0.08); + /* Touch-friendly */ + min-height: var(--touch-min); + display: flex; + flex-direction: column; + justify-content: center; +} + +.card__media { + width: 100%; + aspect-ratio: 16/9; + object-fit: cover; + border-radius: var(--space-sm) var(--space-sm) 0 0; + margin: calc(var(--space-lg) * -1) calc(var(--space-lg) * -1) var(--space-md); +} + +.card__title { + font-size: var(--text-lg); + font-weight: 600; + margin-bottom: var(--space-xs); +} + +.card__meta { + font-size: var(--text-xs); + color: var(--text-secondary); +} +``` + +### 3.6 Masonry (Mobile) + +```css +.masonry { + columns: 2; + column-gap: var(--space-sm); +} + +.masonry-item { + break-inside: avoid; + margin-bottom: var(--space-sm); + border-radius: var(--space-sm); +} + +/* Single column on very small screens */ +@media (max-width: 360px) { + .masonry { columns: 1; } +} +``` + +### 3.7 Swipeable Sections + +CSS scroll-snap для горизонтальных секций. + +```css +.swipe-section { + display: flex; + gap: var(--space-md); + overflow-x: auto; + scroll-snap-type: x mandatory; + scroll-behavior: smooth; + -webkit-overflow-scrolling: touch; + padding: var(--space-md) var(--space-lg); + scrollbar-width: none; /* Firefox */ +} + +.swipe-section::-webkit-scrollbar { + display: none; +} + +.swipe-item { + scroll-snap-align: start; + flex: 0 0 auto; + width: min(280px, 80vw); +} +``` + +**Где использовать:** Карусели, product carousels, category pills, testimonial sliders. + +## 4. Mobile Typography + +### Fluid Typography Formula + +```css +/* Base size scales proportionally */ +--text-base-size: clamp(1rem, 0.875rem + 0.5vw, 1.25rem); + +/* Scale multiplier */ +--scale-ratio: 1.25; /* Major Third */ +--text-sm: calc(var(--text-base-size) / var(--scale-ratio)); +--text-lg: calc(var(--text-base-size) * var(--scale-ratio)); +--text-xl: calc(var(--text-lg) * var(--scale-ratio)); +--text-2xl: calc(var(--text-xl) * var(--scale-ratio)); +``` + +### Line Height & Readability + +```css +/* Body text — line-height для мобильных */ +.text-body { + font-size: var(--text-base); + line-height: 1.6; /* 1.5 на десктопе, 1.6 на мобайле */ + max-width: 65ch; /* Оптимальная ширина строки */ +} + +/* Headings — tighter on mobile */ +.text-heading { + line-height: 1.2; + font-weight: 700; + letter-spacing: -0.02em; +} +``` + +## 5. Touch-Optimized Components + +### Touch-First Button + +```css +.btn { + min-height: var(--touch-min); + min-width: var(--touch-min); + padding: var(--space-md) var(--space-xl); + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-sm); + font-size: var(--text-base); + font-weight: 600; + border: none; + border-radius: var(--space-md); + cursor: pointer; + transition: transform 0.1s ease, background 0.2s ease; + /* Touch feedback */ + -webkit-tap-highlight-color: transparent; + -webkit-touch-callout: none; +} + +.btn:active { + transform: scale(0.98); +} +``` + +### Touch-Optimized Form Input + +```css +.form-input { + min-height: var(--touch-min); + padding: var(--space-md) var(--space-lg); + font-size: var(--text-base); /* Prevents iOS zoom on focus */ + border: 2px solid var(--border-1); + border-radius: var(--space-md); + background: var(--surface-1); + transition: border-color 0.15s ease; + width: 100%; +} + +.form-input:focus { + border-color: var(--primary); + outline: none; + box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 20%, transparent); +} + +/* iOS zoom prevention */ +@media screen and (max-width: 480px) { + .form-input { + font-size: 16px; /* Minimum to prevent zoom on iOS */ + } +} +``` + +## 6. Mobile-First CSS Architecture + +### Base → Component → Layout + +``` +styles/ + base/ + _reset.css /* Normalize, box-sizing */ + _typography.css /* Fluid type scale */ + _colors.css /* CSS Variables, tokens */ + _accessibility.css /* Focus states, reduced motion */ + components/ + _buttons.css /* Touch-optimized buttons */ + _inputs.css /* Form fields */ + _cards.css /* Card patterns */ + _nav.css /* Mobile nav */ + layout/ + _stack.css /* Single-column layout */ + _grid.css /* Responsive grids */ + _sections.css /* Section patterns */ + utilities/ + _spacing.css /* Margin/padding utilities */ + _display.css /* Flex, grid utilities */ + _accessibility.css /* sr-only, reduced-motion */ +``` + +### Reduced Motion + +```css +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} +``` + +## 7. Performance Patterns for Mobile + +### Image Optimization + +```html + +Description + + +``` + +### Critical CSS for Mobile + +```css +/* Inlined in for mobile LCP optimization */ +/* Critical path CSS — < 14KB gzipped */ + +body { + font-family: system-ui, -apple-system, sans-serif; + margin: 0; + padding: 0; + color: var(--text-primary); +} + +.hero { + min-height: 60vh; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: var(--space-xl) var(--space-lg); + text-align: center; +} +``` + +### Lazy Loading with Intersection Observer + +```javascript +// Lightweight lazy loader (no dependencies) +const lazyObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const el = entry.target; + if (el.dataset.src) { + el.src = el.dataset.src; + } + if (el.dataset.bg) { + el.style.backgroundImage = `url(${el.dataset.bg})`; + } + lazyObserver.unobserve(el); + } + }); +}, { + rootMargin: '200px 0px', // Start loading 200px before visible + threshold: 0.01 +}); + +document.querySelectorAll('.lazy').forEach(el => { + lazyObserver.observe(el); +}); +``` + +## 8. iOS & Android Specific Patterns + +### Viewport Meta Tag + +```html + +``` + +### iOS-Specific Fixes + +```css +/* Prevent iOS Safari bounce effect */ +body { + overscroll-behavior-y: contain; + -webkit-overflow-scrolling: touch; +} + +/* Full viewport height fix for iOS */ +.mobile-container { + min-height: 100dvh; /* Dynamic viewport height */ +} + +/* Prevent pull-to-refresh on specific elements */ +.prevent-pull-to-refresh { + overscroll-behavior-y: none; +} +``` + +### Dark Mode (iOS & Android) + +```css +/* System dark mode — auto-applied */ +@media (prefers-color-scheme: dark) { + :root { + --text-primary: #ffffff; + --text-secondary: rgba(255,255,255,0.7); + --surface-1: #1c1c1e; + --surface-2: #2c2c2e; + --border-1: rgba(255,255,255,0.1); + } +} + +/* Force dark mode toggle */ +[data-theme="dark"] { + --text-primary: #ffffff; + --surface-1: #1c1c1e; +} +``` + +## 9. Mobile Testing Checklist + +| Category | Check | Tool | +|----------|-------|------| +| **Layout** | Single-column flow at 320px width | Chrome DevTools | +| **Touch** | All tap targets ≥ 44×44px | Lighthouse | +| **Typography** | No zoom on iOS input focus | Real device test | +| **Performance** | LCP < 2.5s, INP < 200ms, CLS < 0.1 | PageSpeed Insights | +| **Safe Area** | Content doesn't overlap notch/home indicator | iPhone real device | +| **Scroll** | Horizontal scrolls fixed, vertical smooth | Real device test | +| **Forms** | Correct keyboard types for each input | Lighthouse | +| **Dark Mode** | All components readable in dark | Developer tools | +| **Orientation** | Portrait & landscape tested | Real device test | +| **Accessibility** | Touch targets, contrast, labels | axe DevTools | + +## 10. Mobile-First Development Workflow + +### Step 1: Wireframe at 360px + +Начинай всегда с самого маленького экрана. Если не помещается — режь контент. + +### Step 2: Base Styles Mobile-First + +```css +/* Mobile first — base styles for 320px+ */ +.component { + display: flex; + flex-direction: column; + gap: var(--space-md); + padding: var(--space-lg); +} + +/* Tablet landscape & up */ +@media (min-width: 768px) { + .component { + flex-direction: row; + gap: var(--space-xl); + padding: var(--space-2xl); + } +} + +/* Desktop */ +@media (min-width: 1024px) { + .component { + max-width: 1200px; + margin: 0 auto; + } +} +``` + +### Step 3: Progressive Enhancement + +1. **Level 1 (Mobile base):** Content, navigation, forms +2. **Level 2 (Tablet+):** Two-column layouts, side-by-side components +3. **Level 3 (Desktop+):** Animations, complex interactions, floating elements +4. **Level 4 (Desktop+):** Parallax, 3D transforms, advanced effects + +### Step 4: Test on Real Devices + +Chrome DevTools — хорошо для макета, но не для производительности. Всегда тестируй на реальном устройстве: +- iOS Safari (Zoom, pull-to-refresh, safe area) +- Android Chrome (Variable refresh rate, different touch behavior) +- 3G/4G throttling (реальная скорость загрузки) + +## 11. Mobile Anti-Patterns + +| ❌ Плохо | ✅ Хорошо | +|----------|-----------| +| Hamburger-меню для всех навигаций | Bottom tab bar для 3-5 основных разделов | +| Hover-эффекты | Active/pressed states для touch | +| Фиксированные ширины | Fluid typography + container queries | +| Полноценные изображения на всех экранах | Responsive images + lazy loading | +| Inline JS анимации | CSS transitions + GPU-accelerated transforms | +| 100vh для full-height секций | 100dvh (dynamic viewport) | +| Скрытые CTA | Sticky bottom CTA на scrollable страницах | +| Автоплей видео | Play/pause по тапу, muted по умолчанию | +| Popup-формы на мобайле | Inline-формы, progressive disclosure | + +## 12. Mobile Component Library Checklist + +### Базовые компоненты + +- [ ] **Button** — primary, secondary, ghost, icon-only +- [ ] **Input** — text, password, email, tel, number, search +- [ ] **Select** — native select (мобильные picker'ы) +- [ ] **Card** — image card, text card, media card +- [ ] **List** — simple, divider, avatar +- [ ] **Navigation** — bottom tab, top nav, breadcrumbs +- [ ] **Modal** — full-screen on mobile, centered on desktop +- [ ] **Toast/Alert** — dismissible, auto-dismiss +- [ ] **Tab Bar** — scrollable on overflow +- [ ] **Accordion** — expandable sections (FAQ, settings) + +### Mobile-Specific + +- [ ] **Swipeable Carousel** — touch-enabled +- [ ] **Pull-to-Refresh** — native iOS/Android feel +- [ ] **Bottom Sheet** — slide-up from bottom +- [ ] **Floating Action Button** — contextual actions +- [ ] **Sticky Footer** — always-visible CTA + +## 13. Source Links + +- [UXPin: Mobile Navigation Examples](https://www.uxpin.com/studio/blog/mobile-navigation-examples/) +- [UXPin: Mobile Navigation Patterns Pros/Cons](https://www.uxpin.com/studio/blog/mobile-navigation-patterns-pros-and-cons/) +- [James Ross Jr: Responsive Design Best Practices 2026](https://www.jamesrossjr.com/blog/responsive-web-design-best-practices) +- [Scrimba: Responsive Web Design 2026](https://scrimba.com/articles/responsive-web-design-a-complete-guide-2026-2/) +- [DEV: Responsive Design Breakpoints 2025](https://dev.to/gerryleonugroho/responsive-design-breakpoints-2025-playbook-53ih) diff --git a/mobile/mobile-performance-patterns.md b/mobile/mobile-performance-patterns.md new file mode 100644 index 0000000..37718d6 --- /dev/null +++ b/mobile/mobile-performance-patterns.md @@ -0,0 +1,474 @@ +--- +title: Mobile Performance Patterns +created: 2026-07-03 +updated: 2026-07-03 +type: system +tags: [mobile, performance, core-web-vitals, lcp, inp, cls, optimization] +sources: + - https://developers.google.com/search/docs/appearance/core-web-vitals + - https://rankture.com/blog/core-web-vitals-optimization-guide + - https://eastondev.com/blog/en/posts/dev/20251124-core-web-vitals-optimization/ + - https://ubmetrics.com/blog/core-web-vitals-complete-guide-2025 + - https://www.airankaudit.com/core-web-vitals-guide-2025-lcp-inp-cls-explained-with-fixes/ +confidence: high +--- + +# Mobile Performance Patterns — Производительность для мобильных + +## 1. Core Web Vitals: Мобильные Стандарты 2025/2026 + +### 1.1 Thresholds + +| Метрика | Good | Needs Improvement | Poor | +|---------|------|-------------------|------| +| **LCP** | ≤ 2.5s | 2.5s - 4.0s | > 4.0s | +| **INP** | ≤ 200ms | 200ms - 500ms | > 500ms | +| **CLS** | ≤ 0.1 | 0.1 - 0.25 | > 0.25 | + +### 1.2 Mobile-First Importance + +На мобильных: +- LCP **критичнее** из-за медленных сетей (3G/4G) +- INP **критичнее** из-за более слабых CPU +- CLS **критичнее** из-за smaller viewport и layout shifts + +### 1.3 Measurement Tools + +| Tool | Purpose | Best For | +|------|---------|----------| +| PageSpeed Insights | Lab + Field Data | Quick audit | +| Chrome UX Report | Real user data | Production monitoring | +| Web Vitals JS Library | Runtime measurement | Dev instrumentation | +| Lighthouse | Lab testing | Development | +| DevTools Network Throttling | Simulated 3G | Testing | + +## 2. Largest Contentful Paint (LCP) + +### 2.1 What is LCP? + +Время, когда **крупнейший элемент контента** (hero image, H1, section) становится видимым. + +### 2.2 LCP Optimization Checklist + +``` +Priority: +1. Critical image — preload + next-gen format +2. Above-the-fold content — inline critical CSS +3. Server response — reduce TTFB +4. Render-blocking resources — defer/async +5. JS execution — minify + code split +``` + +### 2.3 Critical Image Optimization + +```html + + + + + + + Hero description + +``` + +```css +/* Prevent layout shift for LCP image */ +.lcp-hero { + width: 100%; + aspect-ratio: 16/9; + object-fit: cover; + background: color-mix(in srgb, var(--surface-2) 50%, transparent); +} +``` + +### 2.4 Critical CSS Inlining + +```html + + + + + + + + +``` + +### 2.5 Reduce TTFB (Time to First Byte) + +``` +Optimization: +1. Use CDN (Cloudflare, Fastly, AWS CloudFront) +2. Edge caching (Vercel, Netlify Edge, Cloudflare Workers) +3. HTTP/2 or HTTP/3 +4. Database query optimization +5. Server-side rendering (SSR) or static generation +6. Connection pooling +``` + +## 3. Interaction to Next Paint (INP) + +### 3.1 What is INP? + +Время между **пользовательским взаимодействием** и следующей отрисовкой. Заменяет FID (First Input Delay). + +### 3.2 INP Optimization Strategies + +```javascript +// 1. Use passive event listeners +document.addEventListener('scroll', handler, { passive: true }); +document.addEventListener('touchstart', handler, { passive: true }); + +// 2. Debounce expensive operations +function debounce(fn, delay = 16) { + let ticking = false; + return () => { + if (!ticking) { + requestAnimationFrame(() => { + fn(); + ticking = false; + }); + ticking = true; + } + }; +} + +// 3. Offload heavy work to Web Workers +// worker.js +self.onmessage = (e) => { + const result = heavyComputation(e.data); + self.postMessage(result); +}; +``` + +```css +/* 4. GPU-accelerated animations */ +.animated-element { + will-change: transform, opacity; + transform: translateZ(0); +} + +/* 5. Avoid layout thrashing */ +// Bad: forces layout recalculation +element.style.width = '100px'; +console.log(element.offsetWidth); + +// Good: batch reads +const width = element.offsetWidth; // read +element.style.width = '100px'; // write +``` + +### 3.3 INP Debugging Checklist + +| Category | Check | Tool | +|----------|-------|------| +| **Event handlers** | Passive listeners where possible | DevTools | +| **Long tasks** | < 50ms per task | DevTools Long Tasks | +| **JS execution** | Code splitting, tree shaking | Lighthouse | +| **Layout thrashing** | Batch reads/writes | DevTools | +| **Main thread** | No blocking scripts | Performance panel | + +## 4. Cumulative Layout Shift (CLS) + +### 4.1 What is CLS? + +Суммарный сдвиг **всех визуальных элементов** за время загрузки страницы. + +### 4.2 CLS Prevention Patterns + +```css +/* 1. Always set dimensions on images/videos */ +img, video, iframe { + width: 100%; + height: auto; + object-fit: cover; +} + +/* 2. Reserve space for dynamic content */ +.ad-container { + min-height: 200px; + background: var(--surface-2); +} + +/* 3. Font loading — prevent FOIT/FOUT */ +@font-face { + font-family: 'MyFont'; + src: url('/fonts/myfont.woff2') format('woff2'); + font-display: swap; +} + +/* 4. Sticky ads — fixed position */ +.ad-sticky { + position: fixed; + bottom: 0; + left: 0; + right: 0; +} + +/* 5. Skeleton loaders */ +.skeleton { + background: linear-gradient(90deg, var(--surface-2) 25%, var(--surface-3) 50%, var(--surface-2) 75%); + background-size: 200% 100%; + animation: shimmer 1.5s infinite; + border-radius: 4px; +} + +@keyframes shimmer { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } +} +``` + +### 4.3 CLS Measurement + +```javascript +// Web Vitals library for CLS +import { getCLS } from 'web-vitals'; + +getCLS((value) => { + console.log('CLS:', value); + // Report to analytics + if (value > 0.25) { + sendToAnalytics('cls_threshold_exceeded', { value }); + } +}); +``` + +## 5. Image Optimization + +### 5.1 Image Strategy for Mobile + +``` +Priority: +1. Use next-gen formats (WebP, AVIF) +2. Responsive images with srcset +3. Lazy loading for below-fold images +4. Proper dimensions (width/height) +5. Compress images (Squoosh, TinyPNG) +6. CDN for image delivery +``` + +### 5.2 Responsive Images Pattern + +```html + + + + Description + +``` + +### 5.3 Lazy Loading with Intersection Observer + +```javascript +class LazyLoader { + constructor(options = {}) { + this.threshold = options.threshold || 0.1; + this.rootMargin = options.rootMargin || '200px 0px'; + this.observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const el = entry.target; + + if (el.tagName === 'IMG') { + if (el.dataset.src) el.src = el.dataset.src; + if (el.dataset.srcset) el.srcset = el.dataset.srcset; + } + + if (el.tagName === 'VIDEO') { + if (el.dataset.src) el.src = el.dataset.src; + } + + el.classList.add('loaded'); + this.observer.unobserve(el); + } + }); + }, { + threshold: this.threshold, + rootMargin: this.rootMargin + }); + } + + observe(elements) { + elements.forEach(el => this.observer.observe(el)); + } +} + +// Usage +const lazyLoader = new LazyLoader({ rootMargin: '200px 0px' }); +document.querySelectorAll('.lazy').forEach(el => lazyLoader.observe(el)); +``` + +## 6. CSS & JS Optimization + +### 6.1 CSS Optimization + +```css +/* 1. Use CSS containment */ +.contained { + contain: layout style paint; +} + +/* 2. Minimize reflows */ +.element { + will-change: transform; + transform: translateZ(0); +} + +/* 3. Use CSS variables for theming (avoids JS) */ +:root { + --primary: #0070f3; + --text-primary: #111; +} +``` + +### 6.2 JavaScript Optimization + +```javascript +// 1. Code splitting +const lazyModule = import('./heavy-module.js'); + +// 2. Debounce user input +function debounce(fn, delay = 16) { + let ticking = false; + return () => { + if (!ticking) { + requestAnimationFrame(() => { + fn(); + ticking = false; + }); + ticking = true; + } + }; +} + +// 3. Virtual scroll for long lists +function virtualScroll(container, items, itemHeight) { + const visibleCount = Math.ceil(container.clientHeight / itemHeight); + const scrollTop = container.scrollTop; + const startIndex = Math.floor(scrollTop / itemHeight); + const endIndex = Math.min(startIndex + visibleCount + 10, items.length); + + // Only render visible items + for (let i = startIndex; i < endIndex; i++) { + renderItem(items[i]); + } +} + +// 4. Use requestAnimationFrame for animations +function animateElement(element, keyframes, options) { + element.animate(keyframes, { + ...options, + fill: 'forwards' + }); +} +``` + +## 7. Network Optimization + +### 7.1 Service Worker Caching + +```javascript +// sw.js +const CACHE_NAME = 'mobile-app-v1'; +const ASSETS_TO_CACHE = [ + '/', + '/styles.css', + '/app.js', + '/fonts/*' +]; + +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS_TO_CACHE)) + ); +}); + +self.addEventListener('fetch', (event) => { + event.respondWith( + caches.match(event.request).then((response) => { + return response || fetch(event.request); + }) + ); +}); +``` + +### 7.2 Prefetch & Preconnect + +```html + + + + + + + + + + + +``` + +## 8. Mobile Performance Checklist + +| Metric | Target | Tool | +|--------|--------|------| +| **LCP** | ≤ 2.5s | PageSpeed Insights | +| **INP** | ≤ 200ms | Web Vitals JS | +| **CLS** | ≤ 0.1 | PageSpeed Insights | +| **FCP** | ≤ 1.8s | DevTools | +| **TTFB** | ≤ 600ms | Network panel | +| **Total JS** | ≤ 200KB | Lighthouse | +| **Total CSS** | ≤ 50KB (critical) | Lighthouse | +| **Images** | Next-gen + responsive | PageSpeed Insights | + +## 9. Mobile Performance Anti-Patterns + +| ❌ Плохо | ✅ Хорошо | +|----------|-----------| +| 100vh для полноэкранных секций | 100dvh | +| Полноэкранные изображения | Responsive images + lazy loading | +| Тяжелые JS-библиотеки | Vanilla JS / lightweight alternatives | +| Автоплей видео | Play/pause по тапу | +| Шрифты без font-display | font-display: swap | +| Без сжатия изображений | WebP/AVIF + compression | +| Синхронный JS в | Async/defer |