feat(mobile): comprehensive mobile web design patterns — 6 new articles

- mobile-patterns.md: Mobile layout & pattern systems (18.5KB)
  Fluid typography, container queries, safe-area, CSS architecture,
  touch-optimized components, iOS/Android specifics, testing checklist

- mobile-layout-patterns.md: Mobile layout systems (11.5KB)
  CSS grid, single-column stack, full-bleed hero, card stack,
  swipeable carousel, split-screen, off-canvas nav, bottom sheet,
  FAB, component patterns

- mobile-navigation-patterns.md: Mobile navigation (9.8KB)
  Bottom tab bar, top nav, hamburger menu, bottom sheet nav,
  pill nav, comparison matrix, navigation rules, swipe navigation,
  breadcrumbs, tab content

- mobile-form-patterns.md: Mobile form patterns (10.3KB)
  Input types, keyboards, floating labels, validation patterns,
  progressive disclosure, autocomplete, masked inputs, toggle/radio,
  form anti-patterns

- mobile-interaction-patterns.md: Touch & gestures (16.7KB)
  Swipe detection, long press, pull-to-refresh, touch feedback,
  scroll-reveal, parallax, sticky scroll, CSS scroll animations,
  touch carousel, haptic feedback, passive listeners

- mobile-performance-patterns.md: Performance optimization (12.3KB)
  Core Web Vitals (LCP/INP/CLS), LCP optimization, INP debugging,
  CLS prevention, image optimization, lazy loading, service workers,
  CSS/JS optimization, network optimization

- mobile-landing-patterns.md: Landing page patterns (21.3KB)
  Section sequencing, hero, social proof, feature cards,
  CTA patterns (inline/sticky/full-bleed), testimonials, pricing,
  FAQ accordion, footer, mobile checklist
This commit is contained in:
neo committed 2026-07-03 20:15:10 +05:00
1 parent 76c4c5b4fe
commit 083f6ba783
7 files changed
+4192

No files matched your search

+422
View File
@@ -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
<!-- Email — shows @ and . on keyboard -->
<input type="email" placeholder="you@example.com">
<!-- Phone — shows numeric keypad -->
<input type="tel" placeholder="+7 (___) ___-__-__">
<!-- Number — shows numeric keypad with minus -->
<input type="number" placeholder="100">
<!-- URL — shows .com and / on keyboard -->
<input type="url" placeholder="https://example.com">
<!-- Text — default, full keyboard -->
<input type="text" placeholder="Иван">
<!-- Search — shows search button on keyboard -->
<input type="search" placeholder="Поиск...">
<!-- Password — hides characters -->
<input type="password" placeholder="Пароль">
```
### 2.2 inputmode for Custom Keyboards
```html
<!-- Numeric keyboard without changing type -->
<input inputmode="numeric" pattern="[0-9]*" placeholder="1234">
<!-- Decimal keyboard -->
<input inputmode="decimal" placeholder="123.45">
<!-- Search keyboard -->
<input inputmode="search" placeholder="Искать...">
<!-- Tel keyboard -->
<input inputmode="tel" placeholder="+7 999 123-45-67">
<!-- Email keyboard -->
<input inputmode="email" placeholder="you@example.com">
```
### 2.3 Keyboard Hints
```html
<!-- Enter key label -->
<input type="text" placeholder="Сообщение" enterkeyhint="send">
<!-- Search button -->
<input type="search" placeholder="Поиск" enterkeyhint="search">
<!-- Next (for multi-field forms) -->
<input type="text" placeholder="Имя" enterkeyhint="next">
<input type="text" placeholder="Email" enterkeyhint="next">
<input type="text" placeholder="Телефон" enterkeyhint="done">
```
## 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
<!-- Show at top of form -->
<div class="form-error-summary" role="alert">
<p>Исправьте следующие ошибки:</p>
<ul>
<li><a href="#email">Введите корректный email</a></li>
<li><a href="#phone">Номер телефона должен содержать 10 цифр</a></li>
</ul>
</div>
```
## 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
<input type="text" name="fname" autocomplete="given-name">
<input type="text" name="lname" autocomplete="family-name">
<input type="email" name="email" autocomplete="email">
<input type="tel" name="phone" autocomplete="tel">
<input type="text" name="city" autocomplete="address-level2">
<input type="text" name="postal" autocomplete="postal-code">
```
### 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
<!-- Use native select for mobile — triggers OS picker -->
<select name="country">
<option value="">Выберите страну</option>
<option value="ru">Россия</option>
<option value="by">Беларусь</option>
</select>
<!-- Custom picker for mobile -->
<div class="picker-trigger">
<span class="picker-label">Выберите дату</span>
<span class="picker-arrow">▼</span>
</div>
<div class="picker-overlay">
<div class="picker-wheel">
<div class="picker-scroll">
<!-- OS-style scroll wheel -->
</div>
</div>
</div>
```
### 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 |
+704
View File
@@ -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
<div class="pull-to-refresh">
<div class="ptr-spinner">
<div class="spinner"></div>
</div>
<div class="ptr-label">Обновление...</div>
</div>
<div class="content">
<!-- Page content -->
</div>
```
```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 |
+923
View File
@@ -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
<section class="mobile-hero" style="min-height: 80dvh;">
<div class="container">
<h1 class="hero-title">Заголовок, который решает проблему</h1>
<p class="hero-subtitle">Подзаголовок в 1-2 предложения, объясняющий ценность</p>
<div class="hero-cta-group">
<a href="#signup" class="btn btn-primary">Начать бесплатно</a>
<a href="#demo" class="btn btn-ghost">Смотреть демо ▶</a>
</div>
<div class="hero-social-proof">
<div class="avatars">
<img src="/av1.jpg" alt="" class="avatar">
<img src="/av2.jpg" alt="" class="avatar">
<img src="/av3.jpg" alt="" class="avatar">
</div>
<span class="social-text">+2,000 уже используют</span>
</div>
</div>
</section>
```
```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
<section class="social-proof">
<p class="section-label">Нам доверяют</p>
<div class="logo-grid">
<img src="/logo1.svg" alt="Company" class="logo">
<img src="/logo2.svg" alt="Company" class="logo">
<img src="/logo3.svg" alt="Company" class="logo">
<img src="/logo4.svg" alt="Company" class="logo">
<img src="/logo5.svg" alt="Company" class="logo">
</div>
<div class="stats-grid">
<div class="stat">
<span class="stat-value">10K+</span>
<span class="stat-label">Клиентов</span>
</div>
<div class="stat">
<span class="stat-value">99.9%</span>
<span class="stat-label">Uptime</span>
</div>
<div class="stat">
<span class="stat-value">4.9/5</span>
<span class="stat-label">Рейтинг</span>
</div>
</div>
</section>
```
```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
<section class="features">
<h2 class="section-title">Всё что нужно для роста</h2>
<div class="feature-list">
<div class="feature-card">
<div class="feature-icon">📊</div>
<h3 class="feature-title">Аналитика</h3>
<p class="feature-desc">Мгновенные дашборды с ключевыми метриками</p>
</div>
<div class="feature-card">
<div class="feature-icon">⚡</div>
<h3 class="feature-title">Скорость</h3>
<p class="feature-desc">Загрузка менее чем за 1 секунду</p>
</div>
<div class="feature-card">
<div class="feature-icon">🔒</div>
<h3 class="feature-title">Безопасность</h3>
<p class="feature-desc">Шифрование данных и соответствие GDPR</p>
</div>
</div>
</section>
```
```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
<section class="feature-showcase">
<div class="feature-showcase__content">
<h3 class="feature-showcase__title">Быстрый старт за 2 минуты</h3>
<p class="feature-showcase__desc">Никаких сложных настроек. Подключите API и начните работу.</p>
<a href="#docs" class="btn btn-primary">Документация →</a>
</div>
<div class="feature-showcase__media">
<img src="/screenshot1.webp" alt="Dashboard preview" loading="lazy">
</div>
</section>
<section class="feature-showcase reversed">
<div class="feature-showcase__content">
<h3 class="feature-showcase__title">Автоматизация процессов</h3>
<p class="feature-showcase__desc">Настройте один раз — система будет работать за вас.</p>
<a href="#features" class="btn btn-primary">Узнать больше →</a>
</div>
<div class="feature-showcase__media">
<img src="/screenshot2.webp" alt="Automation preview" loading="lazy">
</div>
</section>
```
```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
<div class="cta-inline">
<div class="cta-inline__content">
<h3 class="cta-inline__title">Готовы начать?</h3>
<p class="cta-inline__desc">Присоединяйтесь к 10,000+ компаний, которые уже используют наш сервис</p>
</div>
<a href="#signup" class="btn btn-primary btn-large">Создать аккаунт</a>
</div>
```
```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
<div class="sticky-cta">
<a href="#signup" class="btn btn-primary sticky-cta__btn">
Начать бесплатно
</a>
<span class="sticky-cta__hint">Без привязки карты</span>
</div>
```
```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
<section class="cta-full-bleed">
<h2 class="cta-full-bleed__title">Готовы трансформировать бизнес?</h2>
<p class="cta-full-bleed__desc">Запросите демо за 2 минуты</p>
<a href="#demo" class="btn btn-white btn-large">Запросить демо</a>
</section>
```
```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
<section class="testimonials">
<h2 class="section-title">Что говорят клиенты</h2>
<div class="testimonial-scroll">
<div class="testimonial-card">
<p class="testimonial-text">"Этот сервис изменил наш подход к аналитике. Всё работает как часы."</p>
<div class="testimonial-author">
<img src="/av1.jpg" alt="Иван П." class="testimonial-avatar">
<div>
<p class="testimonial-name">Иван Петров</p>
<p class="testimonial-role">CEO, TechCorp</p>
</div>
</div>
</div>
<div class="testimonial-card">
<p class="testimonial-text">"Наконец-то удобный интерфейс, который не ломается на телефоне."</p>
<div class="testimonial-author">
<img src="/av2.jpg" alt="Анна С." class="testimonial-avatar">
<div>
<p class="testimonial-name">Анна Сидорова</p>
<p class="testimonial-role">CMO, Digital Agency</p>
</div>
</div>
</div>
</div>
</section>
```
```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
<section class="pricing">
<h2 class="section-title">Простые тарифы</h2>
<div class="pricing-toggle">
<span class="pricing-label active">Ежемесячно</span>
<label class="switch">
<input type="checkbox" id="pricing-toggle">
<span class="switch-slider"></span>
</label>
<span class="pricing-label">Ежегодно (-20%)</span>
</div>
<div class="pricing-cards">
<div class="pricing-card">
<h3 class="pricing-card__name">Старт</h3>
<div class="pricing-card__price">
<span class="price-monthly">₽990</span>
<span class="price-yearly" style="display:none;">₽790</span>
<span class="price-period">/мес</span>
</div>
<ul class="pricing-card__features">
<li>✓ До 1,000 контактов</li>
<li>✓ Базовая аналитика</li>
<li>✓ Email поддержка</li>
</ul>
<a href="#signup" class="btn btn-secondary pricing-card__btn">Начать</a>
</div>
<div class="pricing-card featured">
<div class="pricing-card__badge">Популярный</div>
<h3 class="pricing-card__name">Бизнес</h3>
<div class="pricing-card__price">
<span class="price-monthly">₽2,990</span>
<span class="price-yearly" style="display:none;">₽2,390</span>
<span class="price-period">/мес</span>
</div>
<ul class="pricing-card__features">
<li>✓ Безлимит контактов</li>
<li>✓ Продвинутая аналитика</li>
<li>✓ Приоритетная поддержка</li>
<li>✓ API доступ</li>
</ul>
<a href="#signup" class="btn btn-primary pricing-card__btn">Выбрать</a>
</div>
</div>
</section>
```
```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
<section class="faq">
<h2 class="section-title">Частые вопросы</h2>
<div class="accordion">
<details class="accordion-item">
<summary class="accordion-item__header">
<span>Как начать использовать сервис?</span>
<span class="accordion-icon">+</span>
</summary>
<div class="accordion-item__content">
<p>Регистрация занимает 2 минуты. Просто укажите email, создайте пароль и следуйте инструкциям на экране.</p>
</div>
</details>
<details class="accordion-item">
<summary class="accordion-item__header">
<span>Можно ли отменить подписку?</span>
<span class="accordion-icon">+</span>
</summary>
<div class="accordion-item__content">
<p>Да, вы можете отменить подписку в любой момент из настроек аккаунта. Никаких скрытых платежей.</p>
</div>
</details>
<details class="accordion-item">
<summary class="accordion-item__header">
<span>Есть ли бесплатный пробный период?</span>
<span class="accordion-icon">+</span>
</summary>
<div class="accordion-item__content">
<p>Да, 14 дней бесплатного использования всех функций. Без привязки карты.</p>
</div>
</details>
</div>
</section>
```
```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
<footer class="mobile-footer">
<div class="footer-links">
<a href="/about">О нас</a>
<a href="/blog">Блог</a>
<a href="/careers">Карьера</a>
<a href="/privacy">Конфиденциальность</a>
<a href="/terms">Условия</a>
<a href="/contact">Контакты</a>
</div>
<p class="footer-copyright">© 2026 Company. Все права защищены.</p>
</footer>
```
```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, реальные устройства |
+570
View File
@@ -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
<picture>
<source srcset="image-480.webp 480w, image-800.webp 800w, image-1200.webp 1200w"
media="(min-width: 768px)">
<img
src="image-480.webp"
loading="lazy"
decoding="async"
width="800"
height="600"
alt="Description"
class="lazy-image"
>
</picture>
```
### CSS Optimization
```css
/* Mobile-first critical CSS (inlined in <head>) */
/* 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 по тапу |
+436
View File
@@ -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 |
+663
View File
@@ -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
<!-- Progressive image loading -->
<img
src="thumb.webp"
srcset="small.webp 400w, medium.webp 800w, large.webp 1200w"
sizes="(max-width: 768px) 100vw, 50vw"
loading="lazy"
decoding="async"
width="800"
height="600"
alt="Description"
class="lazy-image"
/>
<style>
.lazy-image {
background: color-mix(in srgb, var(--surface-2) 50%, transparent);
animation: image-reveal 0.4s ease forwards;
opacity: 0;
aspect-ratio: attr(width) / attr(height);
}
@keyframes image-reveal {
to { opacity: 1; }
}
</style>
```
### Critical CSS for Mobile
```css
/* Inlined in <head> 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
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
```
### 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)
+474
View File
@@ -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 image: preload as resource -->
<link rel="preload" as="image" href="/hero-large.webp" fetchpriority="high">
<!-- Responsive hero with next-gen formats -->
<picture>
<source srcset="hero-480.webp 480w, hero-800.webp 800w, hero-1200.webp 1200w"
media="(min-width: 768px)">
<img
src="hero-480.webp"
loading="eager"
fetchpriority="high"
decoding="async"
width="800"
height="600"
alt="Hero description"
class="lcp-hero"
>
</picture>
```
```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
<head>
<!-- Inline critical CSS (above-the-fold) -->
<style>
/* Critical path CSS — target < 14KB gzipped */
.hero {
min-height: 60vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 24px 16px;
}
.hero__title {
font-size: clamp(2rem, 8vw, 4rem);
line-height: 1.1;
margin-bottom: 16px;
}
/* ... more critical styles ... */
</style>
<!-- Defer non-critical CSS -->
<link rel="preload" href="/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/styles.css"></noscript>
</head>
```
### 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
<picture>
<source srcset="image-small.webp 400w, image-medium.webp 800w, image-large.webp 1200w"
media="(min-width: 768px)">
<source srcset="image-small.avif 400w, image-medium.avif 800w, image-large.avif 1200w"
media="(min-width: 768px)">
<img
src="image-small.webp"
srcset="image-small.webp 400w, image-medium.webp 800w, image-large.webp 1200w"
sizes="(max-width: 767px) 100vw, 50vw"
loading="lazy"
decoding="async"
width="800"
height="600"
alt="Description"
class="lazy-image"
>
</picture>
```
### 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
<!-- Preconnect to critical origins -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://api.example.com">
<!-- Prefetch critical resources -->
<link rel="prefetch" href="/next-page.js">
<link rel="prefetch" href="/critical-image.webp">
<!-- Preload critical resources -->
<link rel="preload" href="/hero-large.webp" as="image" fetchpriority="high">
```
## 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 в <head> | Async/defer |