最开始的的方案是:css
body { font-family: 'Helvetica Neue', Helvetica, 'microsoft yahei', sans-serif; }
升级后的方案:html
body { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC","Helvetica Neue",STHeiti,"Microsoft Yahei",Tahoma,Simsun,sans-serif; }
之前iOS版本降级使用 Helvetica。 中文字体设置为华文黑体STHeiTi, iOS 9+ 就开始支持 -apple-system 参数了, Chrome 使用 BlinkMacSystemFont ,兼容 iOS/MacOS。
如今不少设计师的字体都是PingFang,因此这里作了一个兼容。 顺便用"Microsoft Yahei"兼容了一下Window系统。
原生 Android 下中文字体与英文字体都选择默认的无衬线字体。 可是由于安卓系统能够去改系统的默认字体,并且每个手机厂家也会内置字体,因此直接让他去选择默认的吧。不用单独去折腾安卓的字体了。前端
移动端的适配方案各个厂商都一套方案,可是如今主流的方案是阿里的flexible,具体能够去看下这篇文章: 《使用Flexible实现手淘H5页面的终端适配》。
阿里的方案我用过一段时间,可是对于每一个device pixel ratio 都要写一个样式,虽然能够用sass、less的mixmin用法来处理,可是不一样品尺寸屏幕下所显示的文字个数不一致的问题(以下图:商品的标题),每每会致使用户认为这是一个bug。
iPhone4的渲染图html5
iPhone6的渲染图android
因此后面的开发就抛弃了这个方案,选中在之前项目中运用到的方案,思路是跟淘宝的思路大致上是同样的,根据750px的设计稿来换算成rem,1px == 0.01rem;ios
CSS单位rem 在W3C规范中是这样描述rem的: font size of the root element.
也就是根节点的字体的大小,简单的理解,rem就是相对于根元素<html>的font-size来作计算。而咱们的方案中使用rem单位,是能轻易的根据<html>的font-size计算出元素的盒模型大小。css3
具体怎么换算呢?
把750px的设计稿 1px对应0.01rem便可git
思路:github
var html=document.querySelector("html"); html.style.fontSize=html.offsetWidth/7.5+"px" window.onresize=function(){ var a=document.querySelector("html");a.style.fontSize=a.offsetWidth/7.5+"px"; };
注意:并非全部的地方都用rem来处理。
移动端的1px边框。
在处理移动端1px边框的时候有两种方案,其中一种方案就是将initial-scale=0.5还有一种方案就是经过伪类来处理。web
父级 { positon: relative; } 父级:after { content: " "; width: 200%; height: 200%; position: absolute; top: -1px;//之所要写成-1px而不是0是由于这个会将整个框下移1px,因此为了不整个问题将元素上移1px left: 0; z-index: 1; border: 1px solid #ddd; border-bottom: 1px solid #ddd; -webkit-transform: scale(0.5); transform: scale(0.5); -webkit-transform-origin: 0 0; transform-origin: 0 0; }
优化后的方案:
;(function(designWidth, rem2px, fixedWidth) { //容错 designWidth = designWidth || 750;//传入设计稿的宽度 rem2px = rem2px || 100;//rem to px 的关系 fixedWidth = fixedWidth || 0;//固定最大宽度 //获取当前html文档 var docEl = document.documentElement; //获取body var body = document.querySelector("body"); //设置固定宽度的大小 if (!Number(fixedWidth)) { //不存在固定值,或者固定值为0 body.style.maxWidth = designWidth / rem2px + 'rem'; } else { body.style.maxWidth = fixedWidth / rem2px + 'rem'; } body.style.margin = 'auto'; //这里不设置body的position,为了底部存在可让positon:absolute的能够吸在键盘上 //屏幕的宽度 var tempWidth = window.screen.width; var tempHeight = window.screen.height; //最小的宽度,以这个宽度来渲染,能够保证旋转的时候字体大小不变 为何不用文档的宽度, 由于浏览器或者默认的app有时候会占用导航栏, //这个时候宽度和高度就会被裁剪一部分,可是这个时候屏幕的宽高是不会由于浏览器或者app的导航栏而被裁剪 var minWidth = tempWidth > tempHeight ? tempHeight : tempWidth; //手机方向 var orientation = window.orientation; //获取当前默认字体的大小,由于安卓能够设置默认字体的大小来进行计算 var tempDom = window.document.createElement('div'); tempDom.style.width = '1rem'; tempDom.style.display = "none"; var head = window.document.getElementsByTagName('head')[0]; head.appendChild(tempDom); var defaultFontSize = parseFloat(window.getComputedStyle(tempDom, null).getPropertyValue('width')); tempDom.remove(); //设置字体的大小 window.onresize = function() { //延时是由于屏幕旋转获取新的高度须要必定的时间去从新获取高度和宽度 setTimeout(function() { if (typeof(orientation) == "undefined") { //若是不支持orientation 那么就根据屏幕的宽高来判断 var newWidth = window.screen.width; if (newWidth != tempWidth) { tempWidth = newWidth //若是屏幕的宽度值改变了 ReSetFontSize(); } } else { if (orientation != window.orientation) { //设置最新的屏幕方向 为何要延迟,由于文档去从新而且渲染高度是有一个时间段的 orientation = window.orientation; ReSetFontSize(); } } }, 100); }; function ReSetFontSize() { //设置body的高度,body的高度不能直接设置成100%会致使重绘,同时为了防止fiex的bug(键盘弹出) body.style.height = docEl.clientHeight + "px"; //设置字体大小 docEl.style.fontSize = minWidth / designWidth * rem2px / defaultFontSize * 100 + "%"; } ReSetFontSize(); document.body.classList.remove('vhidden'); })(750, 100, 750);
为何这个要单独讲呢,由于这个问题在移动端出现的概率是100%,写习惯了PC端页面的开发者刚开始上手移动端常常会遇到文本垂直居中的问题,明明在谷歌模拟器里面看到文本是垂直居中的,可是为何在手机上看到文字向上偏移的,并且安卓的问题比较大。transform虽然能够实现,可是感受写起来却很是的繁琐。
提供两种方法,
一、padding
p{ /*高度为90px*/ font-size: .26rem; padding: 0.22rem; }
虽然上面的方法能够实现,可是用起来的时候常常每次都要去计算高度(padding值等于设计高度减去font-size以后再/2),这样就比较麻烦,并且,在针对多行的时候还得计算,因而我又采用了下面的方式。
利用 css3 flex布局的特性。
p{ font-size: .26rem; height: 0.9rem; display: flex; display: -webkit-flex; -webkit-align-items:center; align-items:center; box-pack-align: center; -webkit-box-pack-align: center; }
//同时水平居中也能够用下面的css3属性
box-pack: center; -webkit-box-pack: center; -webkit-justify-content: center; justify-content: center;
移动端的布局状况有点多, 切记移动端最好不要使用postion:fixed,由于这个属性会在ios下产生不少bug。最终我根据以前的项目经验简单作了如下2种分类:
什么叫无固定项,所谓的无固定项页面就是整个网页从上到下没有没有固定在页面上的按钮,头部没有固定的按钮,底部没有固定的按钮,左右两侧也没有侧边栏。用户惟一的操做就是滑动页面到底部。这一类直接跟写PC同样的写法就好了。
基本上市面上所看到的移动端的页面都是固定头部和底部,少许的会加入侧边工具栏。这里主要讲固定头部和底部的。
下面的例子主要把页面分为头部,内容,底部三部分。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>test</title> <meta name="keywords" content="test"> <meta name="description" content="test"> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"> </head> <style> * { margin: 0; padding: 0; line-height: 1; border: 0; tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent; -webkit-box-sizing: border-box; box-sizing: border-box; } body { font-size: 0; overflow: hidden; background-color: #f2f2f2; } .vhidden { visibility: hidden; } .flex-box { display: flex; display: -webkit-flex; } .vertical-center { box-pack: center; -webkit-box-pack: center; -webkit-justify-content: center; justify-content: center; } .horizontal-center { -webkit-box-align: center; align-items: center; } input { display: block; height: 0.88rem; font-size: 0.26rem; border: none; width: 100%; text-align: center; } input:focus { border: none; outline: none; } header { height: 1rem; position: relative; z-index: 1; background-color: #fff; } header div { box-flex: 1; -webkit-box-flex: 1; font-size: 0.26rem; width: 100%; } main { -webkit-overflow-scrolling: touch; height: calc(100% - 2rem); overflow-y: scroll; overflow-x: hidden; position: relative; z-index: 1; } main::-webkit-scrollbar { display: none; } main p { padding: 0.2rem; font-size: 0.26rem; color: #333; } footer { height: 1rem; position: relative; z-index: 1; background-color: #fff; } footer div { height: 0.88rem; font-size: 0.26rem; } footer.bottom-input { position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; } .test1 { font-size: .26rem; padding: 0.22rem; text-align: center; background-color: #fff; margin: 0.1rem auto; } .test2 { font-size: .26rem; height: 0.9rem; display: flex; display: -webkit-flex; -webkit-box-align: center; align-items: center; box-pack: center; -webkit-box-pack: center; -webkit-justify-content: center; justify-content: center; background-color: #fff; margin: 0.1rem auto; } </style> <body class="vhidden"> <header class="flex-box"> <div class="flex-box vertical-center horizontal-center">导航栏一</div> <div class="flex-box vertical-center horizontal-center">导航栏二</div> </header> <main> <div class="test1"> 这是内容部分1 </div> <div class="test2"> 这是内容部分2 </div> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <input class="flex-box vertical-center horizontal-center" type="text" name="" id="Input" placeholder="输入点什么"> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> <p>这是内容部分</p> </main> <!-- <footer class="flex-box vertical-center horizontal-center"> <div class="flex-box vertical-center horizontal-center" id="tt">底部按钮</div> </footer> --> <!-- <footer class="flex-box vertical-center horizontal-center bottom-input"> <input class="flex-box vertical-center horizontal-center" type="text" name="" id="Input" placeholder="输入点什么"> </footer> --> </body> <script> (function(designWidth, rem2px, fixedWidth) { //容错 designWidth = designWidth || 750;//传入设计稿的宽度 rem2px = rem2px || 100;//rem to px 的关系 fixedWidth = fixedWidth || 0;//是否固定最大宽度,固定宽度 //若是没有肯定就默认设计稿的宽度 //获取当前html文档 var docEl = document.documentElement; //获取body var body = document.querySelector("body"); //设置固定宽度的大小 if (!Number(fixedWidth)) { //不存在固定值,或者固定值为0 body.style.maxWidth = designWidth / rem2px + 'rem'; } else { body.style.maxWidth = fixedWidth / rem2px + 'rem'; } body.style.margin = 'auto'; //这里不设置body的position,为了底部存在可让positon:absolute的能够吸在键盘上 //屏幕的宽度 var tempWidth = window.screen.width; var tempHeight = window.screen.height; //最小的宽度,以这个宽度来渲染,能够保证旋转的时候字体大小不变 为何不用文档的宽度, 由于浏览器或者默认的app有时候会占用导航栏, //这个时候宽度和高度就会被裁剪一部分,可是这个时候屏幕的宽高是不会由于浏览器或者app的导航栏而被裁剪 var minWidth = tempWidth > tempHeight ? tempHeight : tempWidth; //手机方向 var orientation = window.orientation; //获取当前默认字体的大小,由于安卓能够设置默认字体的大小来进行计算 var tempDom = window.document.createElement('div'); tempDom.style.width = '1rem'; tempDom.style.display = "none"; var head = window.document.getElementsByTagName('head')[0]; head.appendChild(tempDom); var defaultFontSize = parseFloat(window.getComputedStyle(tempDom, null).getPropertyValue('width')); tempDom.remove(); //设置字体的大小 window.onresize = function() { //延时是由于屏幕旋转获取新的高度须要必定的时间去从新获取高度和宽度 setTimeout(function() { if (typeof(orientation) == "undefined") { //若是不支持orientation 那么就根据屏幕的宽高来判断 var newWidth = window.screen.width; if (newWidth != tempWidth) { tempWidth = newWidth //若是屏幕的宽度值改变了 ReSetFontSize(); } } else { if (orientation != window.orientation) { //设置最新的屏幕方向 为何要延迟,由于文档去从新而且渲染高度是有一个时间段的 orientation = window.orientation; ReSetFontSize(); } } }, 100); }; function ReSetFontSize() { //设置body的高度,body的高度不能直接设置成100%会致使重绘,同时为了防止fiex的bug(键盘弹出) body.style.height = docEl.clientHeight + "px"; //设置字体大小 docEl.style.fontSize = minWidth / designWidth * rem2px / defaultFontSize * 100 + "%"; } ReSetFontSize(); document.body.classList.remove('vhidden'); })(750, 100, 750); </script> </html>
Phone手机在滑动overflow-y: scroll的元素上滑动的时候会顿卡,须要加入以下的css代码就能够了
-webkit-overflow-scrolling:touch;
上面的demo在中间部门有输入框而且在底部的时候去点击输入框,弹出的键盘会把输入框盖住,只有在输入部份内容以后输入框才会出如今视窗中。遇到这种状况须要加入以下代码。
var element = document.getElementById("box"); element.scrollIntoView(); //element.scrollIntoView(false); //scrollIntoView 可选参数是 true false,默认是true //true 元素的顶端将和其所在滚动区的可视区域的顶端对齐。 //false 元素的底端将和其所在滚动区的可视区域的底端对齐。
这个bfc不是格式化上下文,而是back forward cache(往返缓存),这个特性最先出如今Firefox和Opera浏览器上,能够在用户使用浏览器的“后退”和“前进”按钮时加快页面的转换速度。
原理就是浏览器会把以前访问的页面缓存到浏览器内存里面,当用在进行“后退”和“前进”操做的时候,浏览器会直接从缓存里面把页面展示到前台,从而不去刷新页面。
可是这样会致使用户在子页面与上一个页面以前存在管理的时候,操做后返回上个页面的状态并无更改。
这个时候咱们就须要去检测页面是否是从缓存里面读取出来的页面。
$(window).on('pageshow', function(event) { if (event.persisted) { location.reload(true); } }); 或者 window.addEventListener("pageshow", funtion(event){ if (event.persisted) { location.reload(true); } });
如今内嵌H5开发的页面愈来愈多,前端跟客户端的交互也就愈来愈多,如今运用得最多的方式是用JSBridge
来通讯,其主要原理就是就是经过某种方式触发一个url(iframe)
的改变,原生捕获到url,进行分析,获得本身想要的数据信息。
之因此要考虑用JSBridge
来通讯是考虑到Android4.2
如下,addJavascriptInterface
方式有安全漏掉
iOS7如下,没法用到ios提供给前端最新的api messageHandlers
由于现有的手机基本上都是4.2以上以及iOS7以上,因此咱们能够放心大胆使用上面两个属性。
var ua = navigator.userAgent.toLowerCase(); window.isAndroid = /android/.test(ua); window.HtmlWebviewCallNative = function(par) { if (/客户端ua标识/.test(ua)) { //判断是否在客户端打开的页面 if (isAndroid) { //Android这个是安卓向浏览器注入的对象,这个看安卓客户端给的是什么 Android.HTMLCallNative(JSON.stringify(par)); } else { window.webkit.messageHandlers.HTMLCallNative.postMessage(par); } } else { console.log(JSON.stringify(par)) } };
//调用方法eg
HTMLCallNative({ functionName: 'callPhone', params: ['13883785984', '18323270482'], callback: 'callbackFunctionName' });
原理以及参数说明
1.经过向window
注册一个名字为HTMLCallNative
的对象,之后每次向这个函数传递要通讯的函数名和函数所需的参数便可;安卓是经过addJavascriptInterface
直接注入页面,ios是经过WKWebView
的新特性MessageHandler
来这个对象来实现JS调用原生方法。
2.约定HTMLCallNative
这个方法名为app中默认用来接受新交互规则的入口函数,安卓和ios分别拿到HTMLCallNative
传过来的function
名字和参数。
3.客户端经过反射机制,查找字符串函数名对应的函数并执行函数,此时通讯成功。
4.functionName
: 必为字符串,驼峰式命名,这个字符串为真正调用的方法,须要前端跟客户端共同来定义。
5.params
:方法须要的参数,无需对参数进行encodeURI
和encodeURIComponent
, 支持字符串,array
,object
。
6.callback
: 有回调函数时,传入这个参数,只须要传入函数名称便可,若回调函数须要传入参数,app端在调用的时候传入便可,跟当时业务相关,这里就不约定格式了。
相比JSBridge
的优势:
1.在JS中写起来简单,不用再用建立iframe
而后触发URL的方式那么麻烦了。
2.JS传递参数更方便。使用拦截URL的方式传递参数,只能把参数拼接在后面,若是遇到要传递的参数中有特殊字符,如&、=、?等,必须得转换,不然参数解析确定会出错。
例如传递的url是这样的:
http://www.baidu.com/share/op...
使用拦截URL 的JS调用方式
loadURL("firstClick://shareClick?title=分享的标题&content=分享的内容&url=连接地址&imagePath=图片地址"); }
将上面的url 放入连接地址这里后,根本没法区分share_uuid
是其余参数,仍是url里附带的参数。
可是使用MessageHandler
就能够避免特殊字符引发的问题。
首先,咱们看下安卓的配置文件和Scheme
<activity android:name = ".MainActivity"> <intent-filter> <action android:name = "android.intent.action.MAIN" /> <category android:name = "android.intent.category.LAUNCHER" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:host="xxx.com" android:scheme="abraham"/> </intent-filter> </activity>
重点关<data android:host="xxx.com" android:scheme="abraham"/>
,前端就须要根据这个字来唤起app
<a href="abraham:/xxx.com/?pid=1">打开app</a>
schema拼接协议的格式:[scheme]://[host]/[path]?[query]
固然ios的也有本身的协议,一般在写唤起app以前须要跟客户端的同事进行对接一下,拿到他们的协议。
固然咱们能够整合一下代码,把ios的也加进来:
var u = navigator.userAgent;var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1; //android终端或者uc浏览器var isiOS2 = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端if (isAndroid) { window.location.href = "安卓提供协议url"; /***打开app的协议,有安卓同事提供***/ window.setTimeout(function() { window.location.href = '下载的地址'; }, 2000); } else if (isiOS2) { window.location.href = "IOS提供协议url"; /***打开app的协议,有ios同事提供***/ window.setTimeout(function() { window.location.href = '下载的地址'; }, 2000); } else { window.location.href = '下载的地址'; }
简单的唤起方法没有解决在其余应用唤起的bug,能够经过下面的唤起 [https://github.com/sunhaikuo/js-arouse-app][4]