Responsive and Mobile Adaptation: viewport / rem / vw / Container Queries
Introduction
Should a frontend architect understand responsive design? My answer is yes — not to memorize every viewport meta, but to build the engineering intuition of “one codebase runs on all devices”. Three reasons:
- Admin backend mobile is a high-frequency scenario. ERP / FinUI / CRM increasingly require PC + tablet + mobile three-end access, responsive is not a “bonus” but a “required question”.
- Viewport misconfig breaks the whole page. Wrong
device-width/ missinginitial-scale/ wronguser-scalable— all make users see a shrunken page. - Architects unify the approach. 5 projects using 5 different rem adaptation schemes, debugging bugs is hard cross-project. Architect’s job is to set one rule, the whole team follows.
This is post #11 of the Frontend Architecture Cultivation Path series. We skip browser-specific device details (like iOS Safari’s rubber-band scrolling), use architecture diagrams + scheme comparison tables + real code to make viewport / rem / vw / container queries / 1px borders / safe areas concrete. The next post deep-dives into build tools (Webpack → Vite → Rspack evolution).
1. viewport meta: The Starting Point for Mobile Adaptation
1.1 What Happens Without viewport
<!-- ❌ No viewport meta: mobile browser assumes desktop width, shrinks render -->
<html>
<head>
<!-- missing viewport meta -->
</head>
Result: iPhone 12 (390pt wide) renders the page at 980px wide, all elements appear 2.5× smaller, user must double-tap to zoom.
1.2 Standard viewport
<meta name="viewport"
content="width=device-width,
initial-scale=1.0,
maximum-scale=1.0,
user-scalable=no,
viewport-fit=cover">
Breakdown:
width=device-width: make layout viewport = device width (most critical)initial-scale=1.0: initial scale 1:1, no shrinkingmaximum-scale=1.0: prevent user zoom (accessibility controversy, usually set 5.0)user-scalable=no: prevent zoom (strong A11y anti-pattern, use carefully)viewport-fit=cover: let page extend into iPhone notch / Dynamic Island area (iOS 11+ required)
Architect’s rule: only use width=device-width, initial-scale=1.0, viewport-fit=cover three-piece set. Don’t touch other parameters (especially user-scalable=no is an A11y anti-pattern).
2. rem Adaptation Scheme: The Classic Mobile Solution
2.1 What is rem
rem = root em, relative to the root element’s (html) font-size.
html { font-size: 100px; } /* common config when design draft is 750px */
.box { width: 1rem; } /* actual 100px */
2.2 Scheme 1: JS Dynamically Set Root Font Size (Most Classic)
// Set root font size = screen width / design draft width * base size
function setRootFontSize() {
const docWidth = document.documentElement.clientWidth;
const baseSize = (docWidth / 750) * 100; // design draft 750px
document.documentElement.style.fontSize = `${Math.min(baseSize, 100)}px`;
// Math.min limits when accessed on PC (no more than 100px)
}
window.addEventListener('resize', setRootFontSize);
setRootFontSize();
Advantages: write dimensions in rem once, auto-responsive to screen width. Disadvantage: JS rendering depends on root font-size setting, first paint may flash.
2.3 Scheme 2: CSS Media Queries (Recommended)
html { font-size: 50px; } /* base */
@media (min-width: 320px) { html { font-size: 42.67px; } }
@media (min-width: 360px) { html { font-size: 48px; } }
@media (min-width: 375px) { html { font-size: 50px; } }
@media (min-width: 414px) { html { font-size: 55.2px; } }
@media (min-width: 768px) { html { font-size: 100px; } }
Advantages: pure CSS, no JS dependency. Disadvantage: step-wise transitions, not smooth; maintaining device lists is painful.
2.4 Scheme 3: CSS Variables + calc (Modern Recommended)
:root {
/* design draft 375px, 1rem = 100px */
--rem-base: calc(100vw / 3.75); /* 100% viewport width / 3.75 = 1rem */
}
@media (min-width: 768px) {
/* PC end limits max font size, prevents too large */
:root {
--rem-base: 100px; /* fixed 100px */
}
}
Advantages: pure CSS, smooth responsive, no JS dependency. Disadvantage: browser may round vw calculations (precision issue, add max-font-size protection in production).
2.5 rem Scheme Selection Decision Table
| Scheme | First-paint flash | Smoothness | PC limit | Recommendation |
|---|---|---|---|---|
| JS dynamic root | ⚠️ Yes | ✅ Smooth | ✅ Needs min() limit | ⭐⭐⭐ |
| Media queries | ❌ None | ⚠️ Stepwise | ✅ Built-in | ⭐⭐ |
| CSS Variables + vw | ❌ None | ✅ Smooth | ⚠️ Needs max limit | ⭐⭐⭐⭐ |
Architect’s recommendation: Scheme 3 (vw + CSS Variables) + postcss-px-to-viewport for auto px conversion.
3. vw Adaptation Scheme: Modern Approach
3.1 Core Idea
1vw = 1% viewport width, directly use viewport width as the unit.
/* Design draft 375px, 1px = 1/375vw, direct conversion */
.title { font-size: calc(100vw / 3.75); } /* 100px on 375px screen */
Advantages: no JS needed at all, smooth responsive. Disadvantage: large screens get too large (e.g., 100px at 1920px → 512px).
3.2 Protect with max-width
.title {
font-size: 16px; /* base for huge screens */
}
/* Under 750px: vw responsive */
/* Note: use @media max-width because vw goes out of control on larger screens */
@media (max-width: 750px) {
.title { font-size: calc(100vw / 3.75); }
}
3.3 postcss-px-to-viewport: Automation
// postcss.config.js
module.exports = {
plugins: {
'postcss-px-to-viewport': {
unitToConvert: 'px', // conversion unit
viewportWidth: 375, // design draft width
unitPrecision: 5, // precision
propList: ['*'], // all properties
viewportUnit: 'vw', // conversion target
fontViewportUnit: 'vw', // fonts use vw
selectorBlackList: ['ignore-'], // exclude ignore- prefixed classes
minPixelValue: 1,
mediaQuery: false,
},
},
};
Effect: source padding: 16px; automatically compiles to padding: 4.2667vw;. Development experience unchanged, runtime auto-responsive.
4. 1px Border Problem: Mobile Visual Trap
4.1 Problem Essence
iPhone 12’s devicePixelRatio = 3, CSS 1px border is rendered as 3 physical pixels — looks “thick” on retina screens.
4.2 Solution: transform scale
.border-1px {
position: relative;
border: none;
}
.border-1px::after {
content: '';
position: absolute;
left: 0; right: 0; bottom: 0;
height: 1px;
background: #ccc;
transform: scaleY(0.333); /* 1 / 3 = 0.333 */
transform-origin: 0 0;
}
/* Multi-direction borders */
@mixin border-1px($color: #ccc, $direction: all) {
position: relative;
&::after {
content: '';
position: absolute;
top: if($direction == bottom, auto, 0);
bottom: if($direction == top, auto, 0);
left: if($direction == right, auto, 0);
right: if($direction == left, auto, 0);
background: $color;
transform: scale(if($direction == top or $direction == bottom, 0.333, 1), if($direction == left or $direction == right, 0.333, 1));
transform-origin: if($direction == right, right, left) if($direction == bottom, bottom, top) 0;
}
}
4.3 Modern Approach: border-image or viewport
/* Scheme 1: viewport unit (auto-responsive to DPR) */
.border-bottom {
border-bottom: 0.333vw solid #ccc;
}
/* Scheme 2: border-image (poor compatibility, not recommended in 2024) */
Production case: an admin backend’s borders looked thick on iPhone 12, switching to transform scale halved the visual weight — CSS physical 1px border vs visual 1px border.
5. Safe Areas: iPhone Notch / Dynamic Island
iPhone X+ top notch + bottom Home Indicator occupy part of the screen, page content directly covering the full screen gets occluded.
5.1 CSS env() Function
/* Top safe distance */
.header {
padding-top: env(safe-area-inset-top); /* 44px on iPhone 12 */
padding-top: max(env(safe-area-inset-top), 0px); /* compatible with non-iPhone */
}
/* Bottom safe distance */
.footer {
padding-bottom: env(safe-area-inset-bottom); /* 34px on iPhone 12 */
}
/* Landscape left/right */
.container {
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}
Key: viewport-fit=cover + env() must appear as a pair — otherwise env() returns 0.
5.2 Fullscreen / Landscape Adaptation
/* Landscape: hide top status bar */
@media (orientation: landscape) {
.header { padding-top: 0; }
}
/* Dark mode adaptation (iOS 13+) */
@media (prefers-color-scheme: dark) {
:root { --bg-color: #1a1a1a; }
}
Production case: an H5 page’s bottom button was blocked by iPhone 14 Pro Max’s Home Indicator, adding env(safe-area-inset-bottom) made the button fully visible.
6. Portrait-Landscape Switching Recalculation
// Listen for both resize + orientationchange
function onResize() {
setRootFontSize(); // recalculate rem
}
window.addEventListener('resize', onResize);
window.addEventListener('orientationchange', () => {
setTimeout(onResize, 100); // delay 100ms, wait for browser to complete switch
});
Key trap: after orientationchange, immediately getting window.innerWidth may return old value (iOS Safari). setTimeout 100ms protection.
7. Container Queries: Component-Level Responsiveness
Container queries were covered earlier (CSS chapter), here emphasizing the mobile scenario:
/* Card component: different layouts in different container widths */
.card-list {
container-type: inline-size;
container-name: card-area;
}
.card {
display: flex;
flex-direction: row;
}
@container card-area (max-width: 400px) {
.card {
flex-direction: column; /* narrow container: cards stack */
}
}
Production case: a mobile H5’s same card component “in main area + in popup”, container queries implement different layouts, no JS width listening.
8. Pitfall Reminders (Senior Architects Please Read Carefully)
- Don’t mix multiple adaptation schemes. rem + vw + media queries mixed, debugging math is unclear. Whole team uses one scheme.
- Don’t forget
viewport-fit=cover. iOS notch / Dynamic Island blocks content, without it the back button in the top status bar is invisible. - Don’t treat
user-scalable=noas default. Visually impaired users depend on zoom, WCAG 2.1 explicitly forbids default-disabled zoom. If you must, provide accessible alternatives (e.g., font size buttons). - Don’t use px directly for borders. On retina screens
border: 1px solidlooks twice as thick. Use transform scale or vw units. - Don’t access
windowin SSR. Next.js / Nuxt SSR runs in Node environment, no viewport. Mobile adaptation must use CSS media queries + vw, not JS-dependent.
Summary
Eight key facts that thread mobile responsive adaptation together:
- viewport meta three-piece:
width=device-width+initial-scale=1.0+viewport-fit=cover. - rem scheme: JS dynamic root / media queries / CSS Variables + vw, vw + CSS Variables is 2024 recommendation.
- vw scheme: pure CSS smooth responsive, with
postcss-px-to-viewportauto-conversion. - 1px borders: when DPR > 1, use
transform: scaleY(0.333), visually thin line. - Safe areas:
env(safe-area-inset-*)+viewport-fit=cover, iPhone notch / Dynamic Island adaptation. - Portrait-landscape switching: after orientationchange setTimeout 100ms recalculate rem.
- Container queries: component-level responsive, mobile H5 popup/main-area shared component’s killer weapon.
- A11y anti-pattern:
user-scalable=nodefault-disabled zoom, WCAG 2.1 forbids.
Next post: Build tool evolution — Webpack 5 / Vite 5 / Rspack 3 / Turbopack, real case breakdown from 12min to 3min.
5 Key Interview Question Tracks
This section maps one-to-one with the article. Q1 ships with a complete answer as a model; the other four are for you to think through — each one is followed by an AI assistant button for a one-click detailed answer.
Q1 (Answer): Which values must the mobile viewport meta tag set? What happens if they’re missing?
A: Three required:
width=device-width(make layout viewport = device width, missing renders at 980px),initial-scale=1.0(initial scale 1:1, missing scales down proportionally),viewport-fit=cover(page extends into iPhone notch / Dynamic Island, missing gets content occluded by status bar). Counter-example: iPhone 12 without viewport renders at 980px, all elements appear 2.5× smaller, user must double-tap to zoom. Architect’s rule: only use these three, don’t touch other parameters — especiallyuser-scalable=nois an A11y anti-pattern (WCAG 2.1 explicitly forbids default-disabled zoom).Q2 (Think): What are the pros and cons of the three rem adaptation schemes (JS dynamic root / media queries / CSS Variables + vw)? Which is recommended in 2024?
Q3 (Think): Why does a 1px border look twice as thick on retina screens? How to fix? Which is better — transform scale or vw units?
Q4 (Think): How do you adapt to iPhone X+‘s notch / Dynamic Island? What’s the working principle of env(safe-area-inset-*)?
Q5 (Think): How do you do responsive in production? How do you combine rem / vw / container queries? What’s the unified scheme across PC / tablet / mobile three ends?
💡 Each question has an AI assistant button — one click gets you a detailed answer.
References
- MDN: Viewport meta tag — viewport meta official reference
- CSS values: env() (MDN) — env() safe area official guide
- CSS Container Queries (web.dev) — container queries practical guide
- Designing for iPhone X (Apple HIG) — iPhone notch adaptation official guide
- postcss-px-to-viewport (GitHub) — px to vw auto-conversion PostCSS plugin
- Mobile Web Performance (Google Developers) — mobile performance optimization complete set