和以往同样,本次项目也放到了 Github 中,欢迎围观 star ~css
1. 前言html
2. 基本概念前端
3. REM布局android
4. VW布局css3
实现单边边框1pxgit
实现多边边框1pxgithub
实现边框圆角web
实现容器固定纵横比浏览器
说到前端页面的布局方案,能够从远古时代的Table布局提及,而后来到 DIV+CSS布局,以后有了Float布局,Flex布局,Column布局,Grid布局等等。
而另外一方面,还有一些 布局概念:
1. 静态布局
直接使用px做为单位
2. 流式布局
宽度使用%百分比,高度使用px做为单位
3. 自适应布局
建立多个静态布局,每一个静态布局对应一个屏幕分辨率范围。使用 @media媒体查询来切换多个布局
4. 响应式布局
一般是糅合了流式布局+弹性布局,再搭配媒体查询技术使用
5. 弹性布局
一般指的是rem或em布局。rem是相对于html元素的font-size大小而言的,而em是相对于其父元素(非font-size的是相对于自身的font-size)
本文不对这些概念作太多的解释说明,主要记录一下整理过程当中比较重要的点
现在移动端布局中免不了要支持高清设备,机型也比较复杂,须要一套比较完善的布局方案来支持(在总体结构上解决多设备宽的适配问题)。
淘宝的 Flexible 让REM布局得以流行开来,而此Flexible实现也有一些不足,此外,也涌现出了多种实现REM布局的方案
好比直接使用 html{ font-size:625%; } 基准值,配合JS来设置根元素字体大小
或者使用媒体查询来设置根元素字体大小
@media screen and (min-width: 320px) { html,body,button,input,select,textarea { font-size:12px!important; } } @media screen and (min-width: 374px) { html,body,button,input,select,textarea { font-size:14px!important; } }
但使用rem来布局的方案并不太正统,它有一些hack的特色
比较规范的方式是使用vw单位,随之而来的就是后起之秀 VW布局
花了一些时间整理了REM布局和VW布局在实际页面中是如何运用的,若是你有兴趣,就往下看吧~
项目地址,欢迎围观~
物理像素又被称为设备像素,它是显示设备中一个最微小的物理部件。每一个像素能够根据操做系统设置本身的颜色和亮度。正是这些设备像素的微小距离欺骗了咱们肉眼看到的图像效果。
设备独立像素也称为密度无关像素,能够认为是计算机坐标系统中的一个点,这个点表明一个能够由程序使用的虚拟像素(好比说CSS像素),而后由相关系统转换为物理像素。
CSS像素是一个抽像的单位,主要使用在浏览器上,用来精确度量Web页面上的内容。通常状况之下,CSS像素称为与设备无关的像素(device-independent pixel),简称DIPs。
屏幕密度是指一个设备表面上存在的像素数量,它一般以每英寸有多少像素来计算(PPI)。
设备像素比简称为dpr,其定义了物理像素和设备独立像素的对应关系。它的值能够按下面的公式计算获得:
设备像素比 = 物理像素 / 设备独立像素
在Javascript中,能够经过 window.devicePixelRatio
获取到当前设备的dpr。
在css中,能够经过 -webkit-device-pixel-ratio
,-webkit-min-device-pixel-ratio
和 -webkit-max-device-pixel-ratio
进行媒体查询,对不一样dpr的设备,作一些样式适配。
或者使用 resolution | min-resolution | max-resolution 这些比较新的标准方式
上图中, Retina为高清设备屏幕,它的一个css像素对应 了4个物理像素
一个位图像素是栅格图像(如:png, jpg, gif等)最小的数据单元。每个位图像素都包含着一些自身的显示信息(如:显示位置,颜色值,透明度等)。
理论上,1个位图像素对应于1个物理像素,图片才能获得完美清晰的展现
如上图:对于dpr=2的retina屏幕而言,1个位图像素对应于4个物理像素,因为单个位图像素不能够再进一步分割,因此只能就近取色,从而致使图片模糊(注意上述的几个颜色值)。
因此,对于图片高清问题,比较好的方案就是两倍图片
(@2x)。
如:200×300(css pixel)img标签,就须要提供400×600的图片。
缩放比 scale
缩放比:scale = 1/dpr
简单的理解,viewport是严格等于浏览器的窗口。在桌面浏览器中,viewport就是浏览器窗口的宽度高度。但在移动端设备上就有点复杂。
移动端的viewport太窄,为了能更好为CSS布局服务,因此提供了两个viewport:虚拟的visualviewport和布局的layoutviewport。
viewport的内容比较深,推荐阅读PPK写的文章,以及中文翻译
视窗缩放 viewport scale
在开发移动端页面,咱们能够设置meta
标签的viewport scale来对视窗的大小进行缩放定义
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
rem单位
font size of the root element.
rem
就是相对于根元素<html>
的font-size
来作计算
视窗单位
兼容性:在移动端 iOS 8 以上以及 Android 4.4 以上得到支持
能够去 Can I use 或 css3test 查看兼容状况
讲的太乱了?本身去看代码
rem布局的核心是设置好根html元素的font-size
通常来讲,为了防止在高清屏幕下像素不够用致使模糊,咱们拿到的设计稿是640px(iphone5 设备宽为320px)或750px的两倍稿(iphone6 设备宽为375px),按照设备宽度作了两倍的大小。
那开发的时候在CSS中要设置什么尺寸呢,如何作到一份设计稿适配到不一样机型中
最佳方案是:在photoshop或其余工具中量出某个元素或图片或文字的尺寸,而后直接写到代码中。额外的适配不须要理会。
width: px2rem(200);
基于此,可使用SCSS来提供一系列的基础支持
/* 移动端页面设计稿宽度 */ $design-width: 750; /* 移动端页面设计稿dpr基准值 */ $design-dpr: 2; /* 将移动端页面分为10块 */ $blocks: 10; /* 缩放所支持的设备最小宽度 */ $min-device-width: 320px; /* 缩放所支持的设备最大宽度 */ $max-device-width: 540px; /* rem与px对应关系,1rem表明在JS中设置的html font-size值(为一块的宽度),$rem即为$px对应占多少块 $px $rem ------------- === ------------ $design-width $blocks */ /* 单位px转化为rem */ @function px2rem($px) { @return #{$px / $design-width * $blocks}rem; } /* 单位rem转化为px,可用于根据rem单位快速计算原px */ @function rem2px($rem) { @return #{$rem / $blocks * $design-width}px; }
为了便于计算,咱们将页面分为10个块,根据映射关系,咱们只须要计算某个元素在页面中占了多少块($rem),结合html中font-size的大小,就能在页面上设置好正确的元素大小
在对应的JS文件中
var docElem = document.documentElement, metaElem = document.querySelector('meta[name="viewport"]'), dpr = window.devicePixelRatio || 1, // 将页面分为10块 blocks = 10, // 须要限制的最小宽度 defaultMinWidth = 320, // 须要限制的最大宽度 defaultMaxWidth = 540, // 计算的基准值 calcMaxWidth = 9999999;
将页面按照clientWidth进行分割成块,和CSS对应起来
// 设置docElem字体大小 function setFontSize() { var clientWidth = docElem.clientWidth; clientWidth = Math.max(clientWidth, defaultMinWidth * dpr) // 调整计算基准值 if (calcMaxWidth === defaultMaxWidth) { clientWidth = Math.min(clientWidth, defaultMaxWidth * dpr); } docElem.style.fontSize = clientWidth / blocks + 'px'; } setFontSize(); window.addEventListener(window.orientationchange ? 'orientationchange' : 'resize', setFontSize, false);
1px在高清屏幕中的显示问题
上图左边设置了css为1px的效果,实际上咱们须要的是右边的效果
明显左边的粗了一些,由于此时1个css像素包含了4个(dpr为2)物理像素,实际须要的是1px的物理像素,而非css像素
为了解决这个问题,有不少方法
在REM布局中广泛采用的是viewport scale 视窗缩放的方式
视窗缩放很简单,其实就是直接将meta标签中的scale进行更改。好比dpr为3,则scale为
但缩放在某些安卓设备中支持度不太好,咱们还须要作其余检测(检测了现用的一些机型,应该还不完整哈)
// 大部分dpr为2如下的安卓机型不识别scale,需设置不缩放 if (navigator.appVersion.match(/android/gi) && dpr <= 2) { dpr = 1; } setScale(dpr); // 企业QQ设置了scale后,不能彻底识别scale(此时clientWidth未收到缩放的影响而翻倍),需设置不缩放 if (navigator.appVersion.match(/qq\//gi) && docElem.clientWidth <= 360) { dpr = 1; setScale(dpr); } docElem.setAttribute('data-dpr', dpr); // 设置缩放 function setScale(dpr) { metaElem.setAttribute('content', 'initial-scale=' + 1 / dpr + ',maximum-scale=' + 1 / dpr + ',minimum-scale=' + 1 / dpr + ',user-scalable=no'); }
同时将最终计算的dpr放到html中,供css作一些特殊适配。看看页面效果
设置容器的最大最小宽度
上图中,随着拉伸,内容区愈来愈大,各元素尺寸也愈来愈大。已经进行了最小宽度的处理。
要控制缩放的程度,关键有两个点:尺寸计算基准、容器宽度
<!DOCTYPE html> <html> <head> <title>REM布局</title> <meta charset="utf-8"> <meta lang="zh-CN"> <meta name="viewport" data-content-max content="width=device-width,initial-scale=1,user-scalable=no"> <link rel="stylesheet" href="./rem.css"> <script src="./rem.js"></script> </head> <body data-content-max> <section class="container">
尺寸计算基准位于 meta标签中的 data-content-max,容器宽度位于 body标签中
在JS中进行匹配控制,须要注意的是,由于咱们已经进行了视窗的缩放,clientWidth将会比设备宽度大,要记得以dpr进行翻倍
// 须要限制的最小宽度 var defaultMinWidth = 320, // 须要限制的最大宽度 defaultMaxWidth = 540, // 计算的基准值 calcMaxWidth = 9999999; if (metaElem.getAttribute('data-content-max') !== null) { calcMaxWidth = defaultMaxWidth; } ... // 设置docElem字体大小 function setFontSize() { var clientWidth = docElem.clientWidth; clientWidth = Math.max(clientWidth, defaultMinWidth * dpr) // 调整计算基准值 if (calcMaxWidth === defaultMaxWidth) { clientWidth = Math.min(clientWidth, defaultMaxWidth * dpr); } docElem.style.fontSize = clientWidth / blocks + 'px'; }
在CSS中,简单地调用一下,核心方法已经抽离
html { @include root-width(); }
/* html根的宽度定义 */ @mixin root-width() { body { @include container-min-width(); &[data-content-max] { @include container-max-width(); } } /* 某些机型虽然设备dpr大于1,但识别不了scale缩放,这里须要从新设置最小宽度防止出现横向滚动条 */ &[data-dpr="1"] body { min-width: $min-device-width; } } /* 设置容器拉伸的最小宽度 */ @mixin container-min-width() { margin-right: auto; margin-left: auto; min-width: $min-device-width; @media (-webkit-device-pixel-ratio: 2) { min-width: $min-device-width * 2; } @media (-webkit-device-pixel-ratio: 3) { min-width: $min-device-width * 3; } } /* 设置容器拉伸的最大宽度 */ @mixin container-max-width() { margin-right: auto; margin-left: auto; max-width: $max-device-width; @media (-webkit-device-pixel-ratio: 2) { max-width: $max-device-width * 2; } @media (-webkit-device-pixel-ratio: 3) { max-width: $max-device-width * 3; } }
要注意的是,这里的max-width也要配上dpr系数
看当作果图
若是仅仅限制计算基准值,容器不限制(将body标签中的属性去掉),就能够实现某种流式效果(另外一种方案)
文本大小是否用rem单位
有时咱们不但愿文本在Retina屏幕下变小,另外,咱们但愿在大屏手机上看到更多文本,以及,如今绝大多数的字体文件都自带一些点阵尺寸,一般是16px和24px,因此咱们不但愿出现13px和15px这样的奇葩尺寸。
咱们能够选择使用px直接定义
/* 设置字体大小,不使用rem单位, 根据dpr值分段调整 */ @mixin font-size($fontSize) { font-size: $fontSize / $design-dpr; [data-dpr="2"] & { font-size: $fontSize / $design-dpr * 2; } [data-dpr="3"] & { font-size: $fontSize / $design-dpr * 3; } }
@include font-size(30px);
固然了,若是要求不严格,也能够直接使用rem单位
讲的太乱了?本身去看代码
REM布局中用到了JS来动态设置html的font-size,可能形成页面的抖动。
能够考虑比较新的VW布局,无需使用JS,虽然说在移动端 iOS 8 以上以及 Android 4.4 以上才得到支持,不过仍是值得一用的。若是须要兼容,能够尝试 viewport-units-buggyfill
在REM布局中处理1px问题是用了视窗缩放的方案,在VW布局中就不用了,转而使用容器缩放(transform)的方案
调用方式形如
height: px2vw(300);
一样的,咱们须要写个转换方法
/* 移动端页面设计稿宽度 */ $design-width: 750; /* 移动端页面设计稿dpr基准值 */ $design-dpr: 2; /* vw与px对应关系,100vw为视窗宽度,$vw即为$px对应占多宽 $px $vw ------------- === ------------ $design-width 100vw */ /* 单位px转化为vw */ @function px2vw($px) { @return ($px / $design-width) * 100vw; } /* 单位vw转化为px,可用于根据vw单位快速计算原px */ @function vw2px($vw) { @return #{($vw / 100) * $design-width}px; }
1. 单边边框
2. 多边边框
3. 边框的圆角
1. 单边边框比较简单,本质是在目标元素上加个伪类,设置宽度(左|右边框)或高度(上|下边框)为1px,而后在高清屏幕下对齐进行缩放
transform-origin: 0 0;
transform: scaleY(.5);
2. 要让伪类支持设置多边边框,已经不能仅仅使用宽度或高度,而应该在这个伪类上设置多边边框,而后设置dpr倍的宽高,再进行缩放(自左上方)
width: 200%;
height: 200%;
transform-origin: top left;
transform: scale(.5, .5);
3. 边框圆角通常做用于多边边框,使用了伪类设置边框以后,元素自己并无边框,因此咱们须要对伪类设置圆角,此外,也须要对元素自己设置圆角
不然就会出现这种尴尬的状况
若是只是须要设置圆角,其实也能够不设置边框,可使用背景颜色来营造出这种“边框”的分界,在VW布局中,显示地设置边框可能会形成代码量太多
另外要注意的是,圆角若是设置为像素值(好比50px),在不一样的dpr下它产生的圆角效果仍是有区别的,因此最好也把dpr做为系数放在圆角中
针对上面三种状况,咱们须要写好一个scss的1px边框生成器
先来看看怎么调用
/* 底部单个边框 */ .f-border-bottom { @include border( $direction: bottom, $size: 1px, $color: #ddd, $style: solid ); }
/* 常规多边边框 */ .f-border { @include border( $direction: all, $size: 1px, $color: #ddd, $style: solid ); }
/* 多个边框不一样的属性 */ &.hover { @include border( $direction: (top, right, bottom, left), $size: (3px, 2px, 1px), $color: (#0f0, #ddd), $style: dotted ); }
/* 圆角边框百分比 */ .f-border-radius { @include border( $direction: all, $radius: 50% ); }
/* 圆角边框自定义多个角,顺序 */ .f-border-radius { @include border( $radius: (10px, 20px, 30px, 40px) ); }
/* 多个边框调用 */ &:not(.info-item__tel) { @include border( $direction: all, $size: 1px, $color: #ddd, $style: solid, $radius: 50px ); }
看起来调用方式仍是有点复杂的,不过应该也还好吧,实在是实现不了像scale缩放那样直接写原生border属性,除非使用构建工具了
这个 border生成器 是怎么实现的呢? Show you the code ..
/** * 元素边框 * @param {string|list} $direction: all 为all或列表时表示多个方向的边框,不然为单个边框 * @param {string|list} $size: 1px 边框尺寸,为列表时表将按照direction的顺序取值 * @param {string|list} $style: solid 边框样式,高清设备下仅支持solid,同上 * @param {string|list} $color: #ddd 边框颜色,同上 * @param {string} $position: relative 元素定位方式,通常为relative便可 * @param {string} $radius: 0 边框圆角 */ @mixin border( $direction: all, $size: 1px, $style: solid, $color: #ddd, $position: relative, $radius: 0 ) { /* 多个边框 */ @if $direction == all or type-of($direction) == list { /* 普通设备 */ @media not screen and (-webkit-min-device-pixel-ratio: 2) { @include border-radius($radius); @if $direction == all { border: $size $style $color; } @else { @for $i from 1 through length($direction) { $item: nth($direction, $i); border-#{$item}: getBorderItemValue($size, $i) getBorderItemValue($style, $i) getBorderItemValue($color, $i); } } } /* 高清设备 */ @media only screen and (-webkit-min-device-pixel-ratio: 2) { @include border-multiple( $direction: $direction, $size: $size, $color: $color, $position: $position, $radius: $radius ); } } /* 单个边框 */ @else { /* 普通设备 */ @media not screen and (-webkit-min-device-pixel-ratio: 2) { border-#{$direction}: $size $style $color; } /* 高清设备 */ @media only screen and (-webkit-min-device-pixel-ratio: 2) { @include border-single( $direction: $direction, $size: $size, $color: $color, $position: $position ); } } }
入口 mixin 判断设备的dpr,而后选择直接生成border代码,或者分发到 border-single 和 border-multiple 中进行高清屏幕的处理
/* 实现1物理像素的单条边框线 */ @mixin border-single( $direction: bottom, $size: 1px, $color: #ddd, $position: relative ) { position: $position; &:after { content: ""; position: absolute; #{$direction}: 0; pointer-events: none; background-color: $color; @media only screen and (-webkit-min-device-pixel-ratio: 2) { -webkit-transform-origin: 0 0; transform-origin: 0 0; } /* 上下 */ @if ($direction == top or $direction == bottom) { left: 0; width: 100%; height: $size; @media only screen and (-webkit-device-pixel-ratio: 2) { -webkit-transform: scaleY(.5); transform: scaleY(.5); } @media only screen and (-webkit-device-pixel-ratio: 3) { -webkit-transform: scaleY(.333333333333); transform: scaleY(.333333333333); } } /* 左右 */ @elseif ($direction == left or $direction == right) { top: 0; width: $size; height: 100%; @media only screen and (-webkit-device-pixel-ratio: 2) { -webkit-transform: scaleX(.5); transform: scaleX(.5); } @media only screen and (-webkit-device-pixel-ratio: 3) { -webkit-transform: scaleX(.333333333333); transform: scaleX(.333333333333); } } } }
/* 实现1物理像素的多条边框线 */ @mixin border-multiple( $direction: all, $size: 1px, $color: #ddd, $position: relative, $radius: 0 ) { position: $position; @include border-radius($radius); &:after { content: ""; position: absolute; top: 0; left: 0; pointer-events: none; box-sizing: border-box; -webkit-transform-origin: top left; @media only screen and (-webkit-device-pixel-ratio: 2) { width: 200%; height: 200%; @include border-radius($radius, 2); -webkit-transform: scale(.5, .5); transform: scale(.5, .5); } @media only screen and (-webkit-device-pixel-ratio: 3) { width: 300%; height: 300%; @include border-radius($radius, 3); -webkit-transform: scale(.333333333333, .333333333333); transform: scale(.333333333333, .333333333333); } @if $direction == all { border: $size solid $color; } @else { @for $i from 1 through length($direction) { $item: nth($direction, $i); border-#{$item}: getBorderItemValue($size, $i) solid getBorderItemValue($color, $i); } } } }
在多边边框中,pointer-events: none 的使用属于核心。为了可以看到伪类的边框,伪类将会被置于元素上方,如此便致使了元素被覆盖不可点击,这个css属性就解除了这个障碍。
圆角若是使用了百分比,就不须要设置dpr系数了
能够看到,不过这么一来,会形成代码比较冗余的问题,特别是当咱们须要再次覆盖以前的边框属性时。
纵横比其实仍是第一次据说,作方案调研设计就一并整合过来了
它主要是用于响应式设计中的iframe、img 和video之类的元素,实现纵横比有不少方法
这里使用 padding-top 百分比的方法,实现一下容器内文本区的固定纵横比
/** * 实现固定宽高比 * @param {string} $position: relative 定位方式 * @param {string} $width: 100% 容器宽度 * @param {string} $sub: null 容器的目标子元素 * @param {number} $aspectX: 1 容器宽 * @param {number} $aspectY: 1 容器高 */ @mixin aspect-ratio( $position: relative, $width: 100%, $sub: null, $aspectX: 1, $aspectY: 1 ) { overflow: hidden; position: $position; padding-top: percentage($aspectY / $aspectX); width: $width; height: 0; @if $sub == null { $sub: "*"; } & > #{$sub} { position: absolute; left: 0; top: 0; width: 100%; height: 100%; } }
/* 容器宽高比 */ .header { @include aspect-ratio( // $width: px2vw(600), // $sub: ".header-content", $aspectX: 375, $aspectY: 150 ) }
padding的百分比是基于元素宽度的,将容器高度设为0,根据盒子模型,则整个元素最终的高度有padding-top来决定
子元素设置绝对定位防止被挤压,同时撑满父级容器,便可实现
从效果图可以看出,美中不足是没法设置容器最大最小宽度,vw是根据设备宽度进行计算的,因此没法解决。
讲的太乱了?本身去看代码
为了解决纯VW布局不能设置最大最小宽度的问题,咱们引入REM。
经过配置html根元素的font-size为vw单位,而且配置最大最小的像素px值,在其余css代码中能够直接使用rem做为单位
调用方式炒鸡简单
html { @include root-font-size(); }
line-height: px2rem(300);
而scss里面的实现,一样是先定义一个映射关系。将页面宽度进行分块(只是为了防止值太大)
/* 移动端页面设计稿宽度 */ $design-width: 750; /* 移动端页面设计稿dpr基准值 */ $design-dpr: 2; /* 将移动端页面分为10块 */ $blocks: 10; /* 缩放所支持的设备最小宽度 */ $min-device-width: 320px; /* 缩放所支持的设备最大宽度 */ $max-device-width: 540px; /* rem与px对应关系,1rem表明html font-size值(为一块的宽度),$rem即为$px对应占多少块 $px $rem ------------- === ------------ $design-width $blocks */ /* html根元素的font-size定义,简单地将页面分为$blocks块,方便计算 */ @mixin root-font-size() { font-size: 100vw / $blocks; body { @include container-min-width(); } /* 最小宽度定义 */ @media screen and (max-width: $min-device-width) { font-size: $min-device-width / $blocks; } /* 最大宽度定义 */ &[data-content-max] { body[data-content-max] { @include container-max-width(); } @media screen and (min-width: $max-device-width) { font-size: $max-device-width / $blocks; } } }
/* 设置容器拉伸的最小宽度 */ @mixin container-min-width() { margin-right: auto; margin-left: auto; min-width: $min-device-width; } /* 设置容器拉伸的最大宽度 */ @mixin container-max-width() { margin-right: auto; margin-left: auto; max-width: $max-device-width; }
这里的max-width直接使用宽度值,由于使用的是vw,视窗未缩放
而在页面标签(html和body)中,简单地配上属性表明是否须要限制宽度便可。
<!DOCTYPE html> <html data-content-max> <head> <title>VW-REM布局</title> <meta charset="utf-8"> <meta lang="zh-CN"> <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"> <link rel="stylesheet" href="./vw-rem.css"> </head> <body data-content-max>
一样是计算基准值和容器宽度两个方面。
若是仅仅限制计算的基准值,也能实现“流式效果”
方案是挺多的,可能也不够完善。要选哪一种方案呢?
每一个方案都能保证在不一样机型下作到必定的适配。
1. 通常来讲,可用直接考虑使用REM布局
2. 因REM使用了JS动态设置html的font-size,且scale对安卓机型不太友好,要求极致的能够选用VW
3. 纯VW布局不支持设置容器最大最小宽高,若是须要此功能则选用 REM + VW布局
怎么使用呢?
可在Github中对应目录的 html,js,css文件,看看是怎么调用的
常规方式是引入公共基础代码,而后在业务代码中调用
在html文件中能够配置 data-content-max 参数来限制最大最小宽度
在scss基础部分还能够自定义这几个值(若是是REM布局的,修改这些值还须要在rem.js 文件中同步修改)
/* 移动端页面设计稿宽度 */ $design-width: 750; /* 移动端页面设计稿dpr基准值 */ $design-dpr: 2; /* 将移动端页面分为10块 */ $blocks: 10; /* 缩放所支持的设备最小宽度 */ $min-device-width: 320px; /* 缩放所支持的设备最大宽度 */ $max-device-width: 540px;