响应式布局实践
2017.09.09 商城正式上线,2017.09.15新版商城经历了第一次抢购活动,还好没出先什么大问题....
一个多月“跪舔”的码农姿态可以告一段落...
往往要在和别人的协作下才能更多更快的发现自我的缺点、优点...
比如:我总是很容易焦虑,即使我知道最后事情总会做好,我还是会焦虑...
再比如:我总是觉得,如果没办法用一句话描述你的需求,只能说明你没有总结过你的想法,而更多的人却习惯在和别人讨论的过程中来总结想法。所以,很多的人认为我不善于沟通。实际呢?对啊!我就是不善于沟通...
扯蛋蛋归扯淡淡,总结还是要写,不要怕犯错,善于总结,快速调整就好了...
关于响应式设计
移动互联网的流量已经超越PC端流量很长一段时间了。
国内很多站点,目前适配PC站点、移动端站点的最多的方案是 http://www. xxx.com 指向pc站点, http:// m.xxx.com 指向移动端站点,当用户访问时通过js判断用户的userAgent做跳转。采取这种方案的原因,一个是出于SEO的考虑,百度站长后台对于一个站点可以提交对应的PC站点和移动端站点,提交对应的移动端站点对百度来说是最友好的,百度也会对pc站点的收录和移动端站点收录做相应的适配,用户通过百度搜索访问站点时,可以根据适配到对应的pc站点链接、移动端站点链接(做了半年站长对SEO可以吹两天)。
虽然可通过js监测用户userAgent来判断用户终端(pc or mobile)后做跳转,但它还是分配了多个域,这意味着你不能不开发两套程序,把再pc站点写过的逻辑、页面,在移动端站点开发时,再“撸”一遍。
而响应式无需监测用户userAgent没有域的切换,只需根据终端类型来适配不同的功能模块与表现样式,它是跨终端的,一个页面适配多个终端。这意味着,所有逻辑、页面、接口,只需要开发一遍可以在所有终端下适配了。这是趋势...
总结:
- PC – http://www. xxx.com
- Mobile – http:// m.xxx.com
- 响应式:PC & Mobile – http://www. xxx.com 无需跳转切换。
物理像素
大家多在问1080p高清屏是什么意思?带着这个问题,我们来了解一下物理像素。(知识来自百科,我只稍作总结)。
所谓1080高清像素是指:分辨率是1920设备像素*1080设备像素,在纵向上排列了1080个显像用的小点,在横向上排列了1920个显像用的小点,同一尺寸的屏幕下,分辨率越高(横向、纵向上的显像点越多越小)屏幕越清晰。
css中的1px
相信所有的前端开发人员对px都了解,这是前端开发中的长度度量单位,浏览器里一切长度都是以px进行度量的。
问题:在高清屏和普通屏下,css的100px展示会不会有差异?
答案是:不会有差异!
在普通的屏幕下1px占用一个设备像素,而在高清屏幕下(如苹果的视网膜屏)1px占用2个设备像素。以iphone6来说物理像素是750 * 1334,因为是高清屏幕,所以用px来度量的话就是 375px * 667px。这个可以在chrome浏览器的调试模式下查看。
常用的css单位
以前开发中最常用到的css单位是px、em、%。随着css3的出现,带来了更多的度量单位,这些单位为响应式开发,带来很大的好处。总结一下常见css单位(不常用的就不整理了)有以下这些(浏览器的兼容性大家可以上 Can I use 查看):
- px: 浏览器的度量单位,相对于物理像素,1px在高清屏幕下可能占用2个物理像素、甚至3个物理像素。
- em: 相对于父元素的font-size,如父元素font-size设置为16px,子元素font-size设置为0.75em,那么转换为px就是0.75 * 16px = 12px;
<div class="parent">
<div class="children"></div>
</div>
.parent {
font-size: 16px;
.children {
width: 0.75em; // 长度将为 0.75 * 16px = 12px
}
- %: 相对于父元素的长度高度,position:fixed 、absolute 除外(fixed将相对于窗口、absolute相对于递归父元素知道 第一个设置了position的元素)
<div class="parent">
<div class="children"></div>
</div>
.parent {
width: 100px;
height: 100px;
.children {
width: 50%; // 宽度为 0.5 * 100px = 50px
height: 50%; // 高度未 0.5 * 100px = 50px
}
- rem: 相对于根节点(一般为html节点)的font-size,如果html节点设置font-size = 100px,那么文档中的元素设置为0.3rem,则计算为:0.3 * 100px = 30px
<html style="font-size: 48px">
<div class="element"></div>
</html>
.element {
width: 5rem; // 5 * 48px = 240px
height: 5rem; // 5 * 48px = 240px
}
- vh/vw: 相对于设备的可视范围,在移动端中经常会用到,比如:设计师经常要求,banner占满首屏高度既:100vh。如iphone6 (375px * 677px)= (100vw * 100vh) ,而iphone6 plus (414px * 736px) = (100vw * 100vh) 两种屏幕下的vw、vh是不一样的。
- clac: css3中的长度计算语法,支持+、-、*、/的计算。
设计稿
要完成响应式开发,要适配更多的设备屏幕,那么光靠前端的是不行的。离不开设计师的的支持。
至少需要有两个版本的设计,1、移动端的设计,2、PC端的设计(如果要做细致一点,充分考虑pad下的响应那么就还要多一版pad下的设计,简单的话,直接用手机端的设计稿去做pad下的适配)。一般移动端设计师给的设计稿的宽度是750px,PC端给到的设计稿宽度是1920px。
大屏幕、中屏幕、小屏幕的划分
这次开发我对屏幕划分成了三种
- 小屏幕 < 769px
- 中屏幕 769px ~ 1366px.. 还有一些人习惯这个期间,有些人设置为(769px ~ 1440px)。
- 大屏幕 1366px ~ 1920px,甚至更大..
定义了大屏幕、中屏幕、小屏幕后,就方便我们写全局的媒体查询范围,如下,我们的做法是用移动端往上兼容(因为移动端的用户比较大,所以,更多的考虑移动端)
// 整体样式 (以移动端设计稿为标准)
@media only screen and (min-width: 769px) {
// 区分移动端pc端的样式
@media only screen and (min-width: 769px) and (max-width: 1366px) {
// 区分中屏幕的样式
@media only screen and (min-width: 1367px){
// 区分大屏幕的样式
}
上面说的都是一些理论的基础的,为了做能到“那里不会,点哪里”还是需要了解一下的。
接下来分享一下我的实际开发步骤:
- meta设置
为页面设置一个布局宽度不是使用默认的宽度,一般做法如下:
<meta name="viewport" content="width=device-width,
initial-scale=1,minimum-scale=1,maximum-scale=1,
user-scalable=no>
使用默认的放大倍数,禁用缩放窗口功能(safari最新版本,并不能禁用两个手指的缩放行为)。
- html font-size 设置
在响应式布局的过程中,我采取的做法是:
- 文字单位用px(出发点在于:同一个14px的文字在320px * 568px 的屏幕下 和 375px * 667px的阅读效果是一样的考虑)。
- banner、图标等设计元素用单位rem进行,这样当窗口打下变化时,只要改变html节点的font-size大小 banner 、图标等用rem设置大小的元素页会跟着变化,做到自是适应屏幕大小的效果。(出于设计的屏幕占比考虑,长宽等比等方面考虑用rem)
- 在官网项目中rem 与 px的转换关系: 320px(目前考虑在内的最小屏幕宽度)的宽度的时候 1rem = 20px,下面是目前在用的一段rem与px实时转换的监听代码。
(function(win,doc){
var docEl = doc.documentElement;
var refreshRem =function (){
var w =docEl.getBoundingClientRect().width || 320;
var fontSize = w/320 * 20;
docEl.style.fontSize = fontSize+'px';
win.fontSize = fontSize;
},refreshRemId;
win.addEventListener('resize', function() {
clearTimeout(refreshRemId);
refreshRemId = setTimeout(refreshRem, 100);
}, false);
win.addEventListener('pageshow', function(e) {
if (e.persisted) {
clearTimeout(refreshRemId);
refreshRemId = setTimeout(refreshRem, 100);
}, false);
refreshRem();
})(window,document);
- 建立合适的流式栅格化系统
所谓的栅格化,即:把网页布局划分成多个网格,比如:常见的栅格系统的栅格数是12栅格,因为12是 1,2,3,4,6的最小公倍数,因此用12栅格系统可以非常顺利做出【2列等分】、【3列等分】、【4列等分】的布局。
栅格系统的写法可以参考bootstrap。或者可以建立一套更适合项目的。目前项目中栅格系统我基于sass循环来建立(适合我们目前的项目),如下:
.mt-row{
@for $i from 1 through 12{
.mt-col-#{$i} {
width: 50%;
float: left;
box-sizing: border-box;
// 清除浮动坍塌造成的影响
&:before, &:after {
display: table;
content: " ";
&:after {
clear: both;
// 强制换行
.mt-row-strict {
@for $i from 1 through 12{
.mt-col-#{$i} {
width: 100%;
float: left;
box-sizing: border-box;
@media only screen and (min-width: 500px) {
@for $i from 1 through 12{
.mt-col-#{$i} {
width: 33.3333333%;
float: left;
box-sizing: border-box;
@media only screen and (min-width: 769px) {
.mt-row{
@for $i from 1 through 12{
.mt-col-#{$i} {
width: (100% / 12) * $i;
}
- 常用的userAgent判断方法
做了多终端适配,难避免的就要在特定的设备下有特殊展示,比如:微信设备(现在可能还要考虑企业微信),那么支付只显示公众号支付;ios设备下,那么下载链接就需要显示为appstore链接等,我讲常用的userAgent整理成了一个工具列如下:
export const IS_MOBILE = (() => {
let regex = /Android|MeeGo|IEMobile|iPhone|iPad|iPod|BlackBerry|Opera Mini|phone|mobile|Nexus|HTC|Sony|Dell|Droid|Motorola|LG|Kindle|SAMSUNG|Pad|Bada|Tizen|Lumia|Touch|BB10|ARM|Windows Phone|Tablet|PlayBook|webOS|KFAPWI|MicroMessenger|MQQBrowser|baidubrowser|UC Browser/i;
return regex.test(navigator.userAgent);
})();
export const IS_WECHAT = (() => {
return (/MicroMessenger/).test(navigator.userAgent);
})();
export const IS_ANDROID = (() => {
return (/Android/).test(navigator.userAgent);
})();
export const IS_IOS = (() => {
return /iPhone|iPad|iPod/i.test(navigator.userAgent);
})();
- 移动端下常见设备的一些问题处理:
- safari下overflow滚动卡顿的问题解决,设置:-webkit-overflow-scrolling: touch;可以解决这个问题。
- 低版本安卓默认浏览器transform卡顿问题,这次项目用vue开发了一些类似swiper的组件(后期会开源)发在低版本安卓系统的浏览器下transform动画滑动会卡段,这时候transform设置为translate3d开启3d加速可以解决这个问题。
- 低版本安卓机字体图标显示异常问题,因为移动优先,所以这次对移动端的兼容性要求较高,几乎测遍了公司的所有安卓机型,发现一个问题,在低版本的安卓下会有字体图标显示会有异常;发了点时间找到了问题:是渲染流畅度的问题。目前我们的字体图片文件(兼容性还算比较好)设置如下:字体图标可以选择到 icomoon上生成。
@font-face {
font-family: 'icomoon';
src: url('./fonts/icomoon.eot?20mzq2');
src: url('./fonts/icomoon.eot?20mzq2#iefix') format('embedded-opentype'),
url('./fonts/icomoon.ttf?20mzq2') format('truetype'),
url('./fonts/icomoon.woff?20mzq2') format('woff'),
url('./fonts/icomoon.svg?20mzq2#icomoon') format('svg');
font-weight: normal;
font-style: normal;
[class^="mt-icon-"], [class*=" mt-icon-"] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'icomoon' !important;
display: inline-block;
speak: none;
text-rendering: auto;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
- 抢购倒计时问题setTimeout,现在的浏览器(拿chrome、safari)等浏览器来说,为了行了优化,当你最小化窗口、或者切换窗口tab、息屏时,浏览器会中断setTimeout的计时,只有当重新切回窗口时记时才会继续,这就会导致记时不准准确的问题。
目前我采取的方案是用 html5 web worker来处理倒计时不准准确的问题,
即:当浏览器 有web worker特性的时候,用web worker去跑倒计时,
而当浏览器没有web woker特性是,认为它不是高级浏览器,因此浏览器本身不会优化setTimeout,
这样就可以兼容绝大部分的浏览器。部分代码如下(基于vue写的,后续倒计时组件会开源)
this.timeout = this.countDown * 1000;
// web worker多线程,防止浏览器切换时,倒计时终止
if (typeof (Worker) !== 'undefined') {
let worker = new Worker('/worker.js');
worker.onmessage = (evt) => {
this.timeout -= 1000;
if (this.timeout <= 0) {
worker.terminate();
this.$emit('timeEnd');
} else {
this.timing();
// ...............
// timing方法
timing () {
setTimeout(() => {
this.timeout -= 1000;
if (this.timeout > 0) {
this.timing();
} else {
this.$emit('timeEnd');
}, 1000);
- 移动端定位当前城市问题,目前市场上很多定位服务(根据ip定位当前城市)。大都数情况下定位是准确的。但是当你用的不是wifi这事运营商网络的时候,这些定位服务,根据ip定位城市就会不准确。那么怎么处理呢?
基于这个项目有引入百度地图的原因,我就立马就决定用地图获取当前城市的位置....代码如下(百度地图采用动态加载的形式载入,vue封装的百度地图,后面也会开源)。
if (IS_MOBILE) {
window.loadBaiduMapOver = () => {
window.hasdBaiduMap = true;
let geolocation = new BMap.Geolocation();
let myGeo = new BMap.Geocoder();
geolocation.getCurrentPosition(function(r) {
if(this.getStatus() == BMAP_STATUS_SUCCESS){
let pt = r.point;
myGeo.getLocation(pt, (result) => {
if (result) {
_this.location = {
province: result.addressComponents.province.replace('省', ''),
city: result.addressComponents.city.replace('市', '')
_this.setCurrentProvince(_this.location.province);
}, (result) => {
// 动态加载百度地图服务,并且只加载一次
if (!window.hasdBaiduMap) {
let script = document.createElement('script');
script.src = 'https://api.map.baidu.com/api?v=2.0&ak=6d5e1930c887a194bb71db14cf6a68e7&callback=loadBaiduMapOver';