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:
1 parent
76c4c5b4fe
commit
083f6ba783
7 files changed
+4192
No files matched your search
@@ -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 |
|
||||
Reference in new issue
Block a user