响应式布局

191 阅读11分钟

1 媒体查询

媒体查询可以使用的属性很多,常用width,height和其他几乎不怎么用或者兼容性不好的本节就不多介绍了,各位可以到MDN上搜索查看相关信息。

基本语法:

aside { width: 200px; float: left; } 
/* 当设备屏幕的宽度小于480px的时候隐藏侧边栏 */ 
@media only screen and (max-width: 480px) { 
    aside { 
        display: none; 
    } 
}

1.1 媒体类型

只需要记住已下三种即可:

  • screen:屏幕
  • print: 打印
  • all: 所有代码示例:控制页头和页脚在打印时不显示。

@media print {
    header, footer {
        display: none; 
    } 
}

1.2 媒体条件

媒体条件有3个,即not、and和or。 代码示例:


/* 如果设备更新频率慢,或者不支持鼠标行为 */ 
@media (update: slow) or (hover: none) {} 
/* 宽度在320px~480px,同时分辨率是150dpi的设备 */ 
@media only screen 
    and (min-width: 320px) 
    and (max-width: 480px) 
    and (resolution: 150dpi) { 
        body { 
            line-height: 1.4; 
        } 
}

1.3 使用媒体查询支持深色模式和关闭动画

1. perfer-color-schema

prefers-color-scheme媒体特性可以用来检测当前网页是否处于

深色模式(或称黑暗模式)中,其支持的参数值如下。

  • no-preference表示系统没有告知用户使用的颜色方案。
  • light表示系统倾向于使用浅色模式。
  • dark表示系统倾向于使用深色模式。

这个属性的兼容性非常好,前端应用支持深色和浅色模式的切换是一个非常值得注意的趋势,所以了解这个属性很有必要。


/* 深色模式 */ 
@media (prefers-color-scheme: dark) { 
body { 
        background: #333; color: white; 
    } 
}
/* 浅色模式 */
@media (prefers-color-scheme: light) { 
    body { 
        background: white; color: #333; 
    } 
}

如果需要在JavaScript代码中对系统的深浅主题进行判断,可以使用 原生的window. matchMedia()方法,例如:


// 是否支持深色模式 
// 返回true或false 
window.matchMedia("(prefers-color-scheme: dark)").matches;

有一种实现深色模式的简单方式,那就是直接把这个颜色反转过来,如果公司里面没有UI佬专门特别的区设计深色模式的配色方案,那么干脆用这个好像也不是不可以:


@media (prefers-color-scheme: dark) { 
    body { 
        filter: invert(1) hue-rotate(180deg); 
        background-color: #000; 
    } 
    img {
        filter: invert(1) hue-rotate(180deg);
    } 
}

以上代码就是把所有的颜色直接使用filter反转过来,然后再把图片的颜色反转回来使之正常显示。

不过filter滤镜在 Safari浏览器中会带来潜在的渲染问题,还是要慎重使用。

2.prefers-reduced-motion

prefers-reduced-motion用来检测操作系统是否设置了关闭不必要动画的操作,其支持的参数值如下。

  • no-preference表示用户没有通知系统任何首选项。

  • reduced表示用户已通知系统,他们更喜欢删除或者替换基于运动 的动画,因为该类型动画会引发前庭功能紊乱患者的不适(类似晕 车),或者一部分人就是单纯动画疲劳,也可能想要更省电。

    代码示例:


@media (prefers-reduced-motion) { 
    .example-1 { animation: none; } 
    .example-2 { transition: none; } 
}

或者:


@media (prefers-reduced-motion) {
    * { 
        animation: none; 
        transition: none; 
    } 
}

1.4 对鼠标行为和触摸行为的支持检测

1 any-hover

any-hover媒体特性可用于测试是否有任意可用的输入装置可以悬 停(就是hover行为)在元素上。例如,鼠标这个输入装置就可以控制 鼠标指针的位置,以及悬停在元素上。因此,下个不太严谨的结论,any-hover其实就是用来检测设备是否接入了鼠标的.

any-hover媒体特性支持下面两个关键字属性值。

  • none表示没有输入装置可以实现悬停效果,或者没有可以实现指向 的输入装置。
  • hover表示一个或多个输入装置可以触发元素的悬停交互效果。

代码示例:下没有鼠标的设备上直接显示图片的提示信息。


figcaption { display: none; } 
figure:hover figcaption { display: block; }
@media (any-hover: none) { 
    figcaption { display: block; } 
}

这个特性兼容性良好,可以放心使用。

2 hover

hover媒体特性的语法和作用与any-hover是一样的,两者的主要 区别在于,any-hover检测任意输入装置,而hover只检测主要的输入装置,这里就不多介绍了。

3 pointer 和 any-pointer

pointer和any-pointer媒体特性主要用于识别当前环境,判断是 否可以非常方便地进行点击操作。

any-pointer支持3个属性值,含义分别如下。

  • none表示没有可用的点击设备。
  • coarse表示至少有一个设备的点击不是很精确。例如,使用手指 操作手机就属于点击不精确。
  • fine表示有点击很精准的设备。例如,用鼠标操作的计算机浏览器。

pointer也支持3个属性值,含义分别如下。

  • none表示主输入装置点击不可用。
  • coarse表示主输入装置点击不精确。
  • fine表示主输入装置点击很精准。

可以根据这两个属性再没有鼠标的设备上增大按钮的可点击范围。

2 环境变量env()

环境变量函数env()规范的制定和兴起是由于iPhone X这类带有“刘海屏”和底部触摸条的移动设备的出现,如果按钮和底部触摸条在一起显示,就会出现交互冲突的问题,而env()函数可以让网页内容显示在设备的安全区域范围。

当然,环境变量函数env()的功能和作用绝不仅仅是设置安全边距这么简单,通过使用env()函数,很多原本需要特殊权限才可以访问的信息就可以作为全局变量在整个页面文档中使用。只是,虽然env()函数的规划很长远,但是由于目前规范还只停留在第一阶段的草案阶段,内容很少,因此目前可以在实践中应用的就只有设置安全边距

env()函数的语法和var()函数的语法很相似,它们的区别在于,env()函数可以用在媒体查询句中,甚至用在选择器中,但var()函数只能作为属性值或作为属性值的一部分。或者说,凡是可以使用var()函数的地方一定可以使用env()函数,但是反过来却不成立。

下面是env()函数的一些使用示意:

/* 直接使用4个安全内边距值 */
env(safe-area-inset-top);
env(safe-area-inset-right);
env(safe-area-inset-bottom);
env(safe-area-inset-left);
/* 使用4个安全内边距值,同时设置兜底尺寸值 */
env(safe-area-inset-top, 20px);
env(safe-area-inset-right, 1em);
env(safe-area-inset-bottom, 0.5vh);
env(safe-area-inset-left, 1.4rem);

注意:

  1. 和通常的CSS属性不同,env()函数中的属性是区分大小写的

  2. 要想使safe-area-inset-*属性表现出准确的间距,一定要确保viewport相关的信息如下:

  3. 
    <meta name="viewport" content="viewport-fit=cover">
    

    目前这个env的兼容性良好。

3.rem 和 vw 单位与移动端适配最佳实践。

终于到了我们最想看到的内容。

不同的设备宽度给布局带来了困扰,虽然弹性布局和网格布局具有宽度自适应性,可以保证布局不乱,但面对文字阅读的体验问题却无能为力。例如,16px大小的文字在375px宽的屏幕下显示正合适,但是在414px宽的屏幕下就会显得偏小,阅读体验不太好。

要想使整个网页保持弹性其实很简单,让元素的宽高和文字的尺寸大小都使用rem单位,然后在不同宽度的设备下设置准确的根字号大小就可以了。

3.1 什么是rem?

rem就是html标签的字体大小,假如html标签的字体大小是50px,那么1rem就是50px,0.5rem就是25px。

以下有两种做法:

1.设定临界点字号。代码如下:


html {
    font-size: 16px;
}
@media (min-width: 414px) {
  html {
    font-size: 18px;
  }
}
@media (min-width: 600px) {
  html {
    font-size: 20px;
  }
}

2.头部嵌入一段JavaScript代码,根据屏幕尺寸设置对应的根字号大小。例如,下面是曾经在业界用过一段时间的计算公式:


document.documentElement.style.width = document.documentElement.clientWidt
h / 7.5 + 'px';

然而,上面两种做法都有不足。

第一种做法对于非临界点尺寸的设备很不友好,例如Pixel 2手机的宽度是411px,不属于任何临界点,代码中使用的根字号是16px,显然偏小,最终的体验就不好

第二种做法的优点在于任何宽度的手机都有对应的根字号设置,但是缺点很多。首先,在我看来,CSS布局效果使用了JavaScript,这是“原罪”;其次,根字号会线性放大,如果是使用iPad等设备访问,则会看到超级夸张的字号和布局尺寸;最后 ,基准字号是50px,与默认****的16px相去甚远,日后想要推翻之前的适配策略(例如改成16px的基准字号)则改动巨大,举步维艰。

3.2 简单了解视区相对单位

视区相对单位指的是相对于浏览器视区尺寸(viewport)的单位,具体包括下面4个。

  • vw——视区宽度百分值。
  • vh——视区高度百分值。
  • vmin——vw或vh,取小的那个值。
  • vmax——vw或vh,取大的那个值。

以最常用的vw单位为例,100vw表示1个视区宽度,在手机的浏览器中,视区宽度就等于手机的像素宽度。例如,在iPhone X中,100vw就等同于iPhone X设备的宽度,由于iPhone X设备宽度是375px,10vw表示视区宽度的1/10,因此在iPhone X中10vw对应的宽度是37.5px。

经典应用:当内容高度不足一屏时,让底部栏贴在浏览器窗口的底部;当内容高度超过一屏时,让底部栏贴在页面最下方。

html


<div class="container">
<content></content>
<footer></footer>
</div>
.container {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
footer {
    margin-top: auto;
}

3.3 calc()函数下的最佳实践

有了vw单位,再配合calc()函数进行计算,无须使用任何JavaScript代码,我们就可以实现基于设备宽度的移动端布局适配方案。例如,希望375px~414px的宽度区间的根字号大小是16px~18px,就可以这么设置:

html {
    font-size: 16px;
}
@media screen and (min-width: 375px) {
html {
    /* 375px宽度使用16px基准尺寸,414px宽度时根字号大小正好是18px */
    font-size: calc(16px + 2 * (100vw - 375px) / 39);
  }
}
@media screen and (min-width: 414px) {
  html {
    font-size: 18px;
  }
}

此时只需要把视觉稿对应的px尺寸使用rem表示就可以了。例如,视觉稿上图片尺寸是120px×80px,则我们布局的时候使用:

img {
  width: 7.5rem;
  height: 5rem;
}   

3px的间隙就可以这样表示:

.container {
  gap: calc(3 / 16rem);
  /* 也可以直接设置成: */
  /* gap:.1875rem; */
}

1.最佳实践范例代码

html {
    font-size: 16px;
}
@media screen and (min-width: 375px) {
html {
    /* 375px作为16px基准,414px宽度时正好对应18px的根字号大小 */
    font-size: calc(16px + 2 * (100vw - 375px) / 39);
  }
}
@media screen and (min-width: 414px) {
html {
  /* 屏幕宽度从414px到1000px,根字号大小累积增加4px(18px-22px) */
  font-size: calc(18px + 4 * (100vw - 414px) / 586);
}
}
@media screen and (min-width: 1000px) {
html {
    /* 屏幕宽度从1000px往后每增加100px,根字号大小就增加0.5px */
    font-size: calc(22px + 5 * (100vw - 1000px) / 1000);
  }
}

当然,随着越来越多的浏览器支持clamp()函数,我们也可以使用下面这种更加精简的语法:

html {
  font-size: 16px;
  font-size: clamp(16px, calc(16px + 2 * (100vw - 375px) / 39), 22px);
}

根字号的变化范围虽然不及@media语法实现得精细,但贵在代码比较精简。

2.rem单位不是万能的

上面的rem单位布局方法并不是万能的,首先渲染尺寸并不总是整数,例如,1.25rem在375px宽度屏幕下的计算值是20px,但是在414px宽度屏幕下的计算值却是22.5px。非整数尺寸偶尔会带来一些渲染的问题,例如,当SVG图标尺寸不是整数的时候,边缘可能会出现奇怪的间隙;又如,需要精确知道若干个列表的高度之和的时候,如果列表的高度不是整数,则最终的高度值和实际的渲染高度值会有误差。在这些场景下,可以将对应元素的rem单位改成px单位进行表示。