响应式:响应不同屏幕设备合适地展现网页效果的方式或着手段
- 桌面端优先vs移动端优先
- rem弹性布局
- 媒体查询
- 响应式导航栏(折叠+粘性布局)实现方案
参考课程:Build Responsive Real-World Websites with HTML and CSS
视频 | 源码
基于React的响应式单页应用开发、优化和部署
桌面端优先vs移动端优先
目前更常用的是移动端优先,本项目采用桌面端优先
|
桌面端优先 |
移动端优先 |
设计理念 |
先设计大屏幕的网页,然后逐步适配到小屏幕 |
先小再大 |
实现方式 |
基于max-width |
基于min-width |
|
@media screen (max-width: 1200px) |
@media screen (min-width: 600px) |
rem弹性布局
DOM根元素html中的字体大小,如果没有定义,则是浏览器默认字体大小:16px
1 2 3 4 5 6 7 8
| html{
font-size: 62.5%; }
|
通过上面的转换,将html元素的字体大小转换为10px,即1rem=10px
可以把font-size, width, padding, margin之类的值都设置为以rem为单位,这样需要统一修改布局大小的时候(例如适配小屏幕)直接修改html元素的font-size就行
媒体查询
注意单位:rem & em
- 在media query中不是基于HTML元素的设置的
- 在这里固定 1rem=1em=16px
- 媒体查询中rem有问题,一般使用em
桌面端优先和移动端优先的差异:
- 桌面端优先,用max-width
- 移动端优先,用min-width
1 2 3 4 5 6 7
| @media screen and (max-width:84em) { .hero { max-width: 120rem; } // ... }
|
只要满足要求,所有媒体查询都会生效,后定义的会覆盖前定义的,所以顺序很重要!
breakpoint
一般断点可以认为是1200 900 600 300之类的
开发时可以直接用chrome调试断点,推荐Responsive模式
- 可以从大到小拉视窗,找到布局出现问题的大致宽度,然后在附近的位置加断点
- 一般一个媒体查询生效的范围差不多在200px左右,也就是两个端点之间差不多隔200px
参考:
1 2 3 4 5 6 7 8 9 10 11
| // 等于或大于 34*16 = 544px(手机横屏) @media (min-width: 34em) { ... }
// 等于或大于 48*16 = 768px(平板竖屏) @media (min-width: 48em) { ... }
// 等于或大于 62*16 = 992px(pc窄屏) @media (min-width: 62em) { ... }
// 等于或大于 75*16 = 1200px( pc宽屏) @media (min-width: 75em) { ... }
|
实现方案
以实现桌面端到移动端的导航栏为例,响应式是实现方案一般是
- 满足触发条件后,给需要改变样式的元素重写样式,样式的交互动画可以新增css类实现
- 满足触发条件后,在body元素加上某一css类,然后用嵌套css类选择器定义新样式
折叠导航栏
场景:
视窗宽度较小时导航栏折叠起来,通过点击按钮展开
思路
- 导航栏中的连接都包在一个flex容器里(
main-nav
)
- 媒体查询重写flex容器的样式(
flex-direction
, gap
, font-size
, …)
- 给按钮绑定事件
- 事件触发会给元素加上或删去css类(
nav-open
)
- 在css类里控制导航栏的偏移 (
position: fixed
,注意父元素要position: relative
)
- 打开导航,则移入视窗 (
transform: translateX(0)
)
- 关闭导航,则移出视窗 (
transform: translateX(100%)
)
- 设置渐入渐隐效果
- 动画:
transition: all 0.5s ease-in
- 透明度:
opacity: 0/1
- 事件触发:
pointer-events: none/auto
- 不被屏幕阅读器识别:
visibility: hidden/visible
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| const Header = () => { const [isNavOpen, setIsNavOpen] = useState(false)
function onClickHandle(e) { setIsNavOpen(!isNavOpen) }
function onCloseHandle(e) { setIsNavOpen(false) }
return ( <header className={'header ' + (isNavOpen ? 'nav-open ' : '')}> <NavLink to="#"> <img className='logo' src={Logo} alt="omnifood logo" /> </NavLink> <nav className='main-nav'> <ul className='main-nav-list'> <li><NavLink className='main-nav-link' to="how" onClick={onCloseHandle}>How it works</NavLink></li> <li><NavLink className='main-nav-link' to="meals" onClick={onCloseHandle}>Meals</NavLink></li> <li><NavLink className='main-nav-link' to="testimonials" onClick={onCloseHandle}>Testimonials</NavLink></li> <li><NavLink className='main-nav-link' to="pricing" onClick={onCloseHandle}>Pricing</NavLink></li> <li><NavLink className='main-nav-link nav-cta' to="cta" onClick={onCloseHandle}>Try for free</NavLink></li> </ul> </nav>
<button className='btn-mobile-nav' aria-label='open navigation' onClick={onClickHandle}> <i className='icon-mobile-nav iconfont icon-menu' name="btn-menu"></i> <i className='icon-mobile-nav iconfont icon-close' name="btn-close"></i> </button> </header> ) }
|
CSS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| @media screen and (max-width:59em) { .main-nav { position: fixed; background-color: rgba(255, 255, 255, 0.9); -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); top: 0; left: 0; width: 100%; height: 100vh; transform: translateX(100%); z-index: 10;
display: flex; align-items: center; justify-content: center;
transition: all 0.5s ease-in; // 1. 透明度改为0 opacity: 0; // 2. 取消事件触发 pointer-events: none; // 3. 对屏幕阅读器不可见 visibility: hidden;
}
.nav-open {
.main-nav { opacity: 1; pointer-events: auto; visibility: visible; transform: translateX(0); }
.icon-mobile-nav {
&[name="btn-close"] { display: block; }
&[name="btn-menu"] { display: none; } } } }
|
粘性导航栏
场景:
滚动到顶部时,导航栏在标准流中,滚动到一定位置时,导航栏脱标固定在顶端
思路
- 给body元素添加css类
sticky
- 滚动到顶部时,移除
sticky
类,否则,加上 sticky
类
- 事件应该写在导航栏元素之后的第一个元素(
section-hero
)
- 修改元素的样式,使用嵌套选择器
.sticky .xxx
,这样就能使sticky元素影响所有的元素
- 导航栏的样式要修改
- 导航栏以后第一个元素要(
section-hero
)加上 margin-top
代码
方案1:IntersectionObserver
见上一篇scroll方案,这个方案丑,放弃了
方案2:scrollTop
判断scrollTop是不是等于0
JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| const Hero = () => { const [isTop, setIsTop] = useState(true)
useEffect(() => { function scrollHandle() { const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; if (scrollTop === 0) { setIsTop(true) } else { setIsTop(false) } } window.addEventListener('scroll', scrollHandle) return () => { window.removeEventListener('scroll', scrollHandle) }
}, [])
useEffect(() => { if (isTop) { document.body.classList.remove('sticky') } else { document.body.classList.add('sticky') } }, [isTop]) }
|
CSS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| .sticky { .header { position: fixed; width: 100%; top: 0; bottom: 0; height: 8rem; padding-top: 0; padding-bottom: 0; z-index: 9; background-color: rgba(255, 255, 255, 0.95); box-shadow: 0 1.2rem 3.2rem rgba(0, 0, 0, 0.05);
}
.section-hero { margin-top: 8rem; } }
|