CSS进阶实战4:响应式实现思路

响应式:响应不同屏幕设备合适地展现网页效果的方式或着手段

  • 桌面端优先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)

image-20220428143726604

rem弹性布局

DOM根元素html中的字体大小,如果没有定义,则是浏览器默认字体大小:16px

1
2
3
4
5
6
7
8
html{
/*
效果:1rem = 10px
计算方法:默认为16px
10/16 *100%=62.5%
*/
font-size: 62.5%;
}

通过上面的转换,将html元素的字体大小转换为10px,即1rem=10px

可以把font-size, width, padding, margin之类的值都设置为以rem为单位,这样需要统一修改布局大小的时候(例如适配小屏幕)直接修改html元素的font-size就行

媒体查询

@media

注意单位:rem & em

  • 在media query中不是基于HTML元素的设置的
  • 在这里固定 1rem=1em=16px
  • 媒体查询中rem有问题,一般使用em

桌面端优先和移动端优先的差异:

  • 桌面端优先,用max-width
  • 移动端优先,用min-width
1
2
3
4
5
6
7
/* 1344px 较小pc */
@media screen and (max-width:84em) {
.hero {
max-width: 120rem;
}
// ...
}

只要满足要求,所有媒体查询都会生效,后定义的会覆盖前定义的,所以顺序很重要!

breakpoint

一般断点可以认为是1200 900 600 300之类的

开发时可以直接用chrome调试断点,推荐Responsive模式

  • 可以从大到小拉视窗,找到布局出现问题的大致宽度,然后在附近的位置加断点
  • 一般一个媒体查询生效的范围差不多在200px左右,也就是两个端点之间差不多隔200px

image-20220513190413121

参考:

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类选择器定义新样式

折叠导航栏

场景:

视窗宽度较小时导航栏折叠起来,通过点击按钮展开

image-20220513191603694

思路

  • 导航栏中的连接都包在一个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元素影响所有的元素
    • 导航栏的样式要修改
      • position: fixed
    • 导航栏以后第一个元素要(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;
// console.log(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;
}
}
------ 本文结束 ❤ 感谢你的阅读 ------
------ 版权信息 ------

本文标题:CSS进阶实战4:响应式实现思路

文章作者:Lury

发布时间:2022年05月13日 - 21:23

最后更新:2022年05月14日 - 20:12

原始链接:https://luryzhu.github.io/2022/05/13/CSS/omnifood4_responsive/

许可协议:署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。