移动端H5填坑指南
来源:互联网 发布:浙大网络线怎么用 mac 编辑:程序博客网 时间:2024/05/16 17:33
移动端H5填坑指南
移动端H5应用,开发过程中主要遇到的问题:
1.适配不同手机
答:手淘方案(rem版本)
2.布局(固定位置、显示隐藏、栅栏)
答:使用position、visibility和display、flex
3.下拉刷新和上滑加载
答:touchstart、touchmove、touchend和scroll实现
4.缓存数据
答:使用localStorage
5.跳转与返回
答:location和history
6.输入与虚拟键盘
下面详细说说:
1.适配不同手机
移动端头疼的一点,就是适配问题,这里关注的是手机分辨率,不同的手机分辨率不一样,苹果手机更是用上了视网膜屏,比如iphone6的DPR是2,6s的DPR是3。具体参考使用Flexible实现手淘H5页面的终端适配,当然,现在已经有更好的方法,那就是使用vw和vh。
手淘方案中用到的是rem这个CSS单位,1 rem等于body.fontSize
的大小,默认值是16px,最小值为12px,主要分成两步
1.计算设备实际分辨率,从而得出body.fontSize
(rem的基数)并动态写入。首先在网页上写入width=device-width
,声明页面宽度为设备宽度,对于Android手机,根据不同设备的分辨率,将宽度除以10就是该设备的rem基数,对于iOS,则还需要计算dpr,所以其实际分辨率为设备宽度*dpr,设备高度*dpr,rem基数设备宽度*dpr/10。
2.元素的大小使用rem为单位。通过上一步,将设备的实际分辨率宽度看作10rem,如果一个元素在一个设备中为1rem,那么在另一个设备中只要也是1rem,就能保证其相对大小是一样的(其实和等比例缩放差不多)。我们只需要计算UI图上元素的rem即可复用,如笔者项目中一级标题字体大小为0.33rem,间距是0.44rem等。
引用的方式如下:
<meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <script src="../common/framework/js/flexible-0.3.2.debug.js" type="text/javascript"></script> <link rel="stylesheet" type="text/css" href="../common/framework/css/flexible-0.3.2.debug.css">
有一点建议,引用该方案后可能需要添加div{font-size:0}
,不然Chromium中ipone模拟时div的上内边距会有一定空白(实际设备上没试过,可能不会有影响,但是浏览器上看着不爽就改了)。
2.布局(固定位置、显示隐藏、栅栏)
position、visibility和display、flex都是CSS的属性。
固定区域滑动的实现需要滑动的内容为absolute,父级为fixed,而当父元素为relative而子元素为absolute(absoulte会找最近的父级realative直到为空)时可实现左右滑动。固定区域滑动实现如下
<div class="content"> <div class="ground-container"> <div class="ground-panel"> <div class="ground ground-red"> </div> <div class="ground ground-blue"> </div> </div> </div></div>
.ground-container{ position: fixed; top: 10%; bottom: 10%; left: 0; right: 0;}.ground-panel{ position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow: auto;}.ground{ width: 100%; height: 720px;}.ground-red{ background-color: red;}.ground-blue{ background-color: blue;}
元素的显示和隐藏是常见的事,一般在JQuery中使用$(id).show和$(id).hide()即可,但这种隐藏有可能不是想要的,这时就需要visibility了。
举个例子,一般登录校验的时候,密码错误会有相应的提示。假设这个提示本身是隐藏的,在密码输入栏和确定按钮的中间,只有校验出错的时候才显示。这是如果使用$(id).show和$(id).hide()会发现确定按钮会被挤下去,位移了一段距离。这是因为$(id).show和$(id).hide()实际上是封装了display:block和display:none的,display:none会将元素隐藏不可见,从渲染后的页面中也不会预留位置。而我们想要的隐藏只是不可见而已,元素还是在相应的位置的,这是就需要visibility了,即设置为visibility:visible或者是visibility:hidden使其显示或隐藏。笔者认为$(id).show和$(id).hide()比较适合弹窗和下拉导航一类的,visibility则适合提示一类。
相比较百分比设置,流式布局更适合使用flex。一旦遇到如三列布局这样的,需要自己计算各列的百分比是比较麻烦的,而flex就自动做好了这件事。同时还能有各种各样的显示方式。实现代码如下:
<div class="panel"> <div class="item">item1</div> <div class="item">item2</div> <div class="item">item3</div></div>
.panel{ display: -webkit-box; display: -ms-flexbox; display: -webkit-flex; display: flex;}.item{ -webkit-box-flex:1; -webkit-flex:1; flex:1;}
3.下拉刷新和上滑加载
touchstart、touchmove、touchend和scroll
笔者使用的是jQuery WeUI、中的扩展组件很强大,但是有一点,其中的下拉刷新和上滑加载会冲突,当不处于顶部的时候,下滑仍然会触发刷新开发中就遇到这样的问题,触摸实现的下拉刷新和滑动事件冲突,倒是直接向下滑动时会触发下拉刷新,解决方案就是计算触摸移动的距离,判断是否下滑,这个在touchend中实现,同时在touchmove中根据移动距离判断是否阻止传递。这是因为在事件流touchstart-touchmove-touchend-click中,event.preventDefault()会阻止传递,同时position、z-index也会影响事件。
笔者项目中实现代码如下
<div class="container"> <!--头部标题栏--> <div class="head_div"> <div class="head_title_2"><span > 标题</spani></div> <div class=" head_btn" onclick="add()" ><img src="../resources/img/add_gray.png"></div> </div> <!--内容--> <div id="content" class="content weui-pull-to-refresh"> <div class="content_placeholder"> <!-- 头部占位区域 --> </div> <div class="weui-pull-to-refresh__layer"> <div class='weui-pull-to-refresh__arrow'></div> <div class='weui-pull-to-refresh__preloader'></div> <div class="down">下拉刷新</div> <div class="up">释放刷新</div> <div class="refresh">正在刷新</div> </div> <!-- 详细信息 --> <div id="tabContent" class="tab_content"> </div> <div class="footer_placeholder"> <!-- 底部占位区域 --> </div> </div></div>
$('.content').pullToRefresh().on("pull-to-refresh", function() { pullDownFlag = true; refreshPage();});var prev=0, current=0;$(window).scroll(function() { var bottomX = $(document).height() - $(window).height(); current = $(window).scrollTop(); if(current>prev){ console.log('下滑'); } prev = current; if(current<=0){ console.log("滚动条已经到达顶部为" + bottomX); show(); } if (current >= bottomX) { console.log("滚动条已经到达底部为" + bottomX); //if(lock) add(); show();add(); } prev = current;});
引用JQuery-weui
部分如下
/** * jQuery WeUI V1.0.1 * By 言川* http://lihongxun945.github.io/jquery-weui/ *//* global $:true *//* global WebKitCSSMatrix:true */(function($) { "use strict"; $.touchEvents = { start: $.support.touch ? 'touchstart' : 'mousedown', move: $.support.touch ? 'touchmove' : 'mousemove', end: $.support.touch ? 'touchend' : 'mouseup' }; $.getTouchPosition = function(e) { e = e.originalEvent || e; //jquery wrap the originevent if(e.type === 'touchstart' || e.type === 'touchmove' || e.type === 'touchend') { return { x: e.targetTouches[0].pageX, y: e.targetTouches[0].pageY }; } else { return { x: e.pageX, y: e.pageY }; } }; $.fn.join = function(arg) { return this.toArray().join(arg); }})($);/* ===============================================================================************ Pull to refreh ************=============================================================================== *//* global $:true */+function ($) { "use strict"; var PTR = function(el) { this.container = $(el); this.distance = 50; this.attachEvents(); } PTR.prototype.touchStart = function(e) { //if($(window).scrollTop()>100) return; if(this.container.hasClass("refreshing")) return; var p = $.getTouchPosition(e); this.start = p; this.diffX = this.diffY = 0; }; PTR.prototype.touchMove= function(e) { if(this.container.hasClass("refreshing")) return; if(!this.start) return false; var p = $.getTouchPosition(e); this.diffX = p.x - this.start.x; this.diffY = p.y - this.start.y; if(this.diffY < 0) return; this.container.addClass("touching"); //修改,与body内div的scroll兼容 start if($(document.body).scrollTop()<this.distance){ e.preventDefault(); e.stopPropagation(); this.diffY = Math.pow(this.diffY, 0.8); this.container.css("transform", "translate3d(0, "+this.diffY+"px, 0)"); } //修改,与body内div的scroll兼容 end if(this.diffY < this.distance) { this.container.removeClass("pull-up").addClass("pull-down"); } else { this.container.removeClass("pull-down").addClass("pull-up"); } }; PTR.prototype.touchEnd = function() { this.start = false; if(this.diffY <= 0 || this.container.hasClass("refreshing")) return; this.container.removeClass("touching"); this.container.removeClass("pull-down pull-up"); this.container.css("transform", ""); //新增判断,不再顶部一定范围内不可下拉刷新 if($(document.body).scrollTop()>this.distance || Math.abs(this.diffY) <= this.distance) { } else { this.container.addClass("refreshing"); this.container.trigger("pull-to-refresh"); } }; PTR.prototype.attachEvents = function() { var el = this.container; el.addClass("weui-pull-to-refresh"); el.on($.touchEvents.start, $.proxy(this.touchStart, this)); el.on($.touchEvents.move, $.proxy(this.touchMove, this)); el.on($.touchEvents.end, $.proxy(this.touchEnd, this)); }; var pullToRefresh = function(el) { new PTR(el); }; var pullToRefreshDone = function(el) { $(el).removeClass("refreshing"); } $.fn.pullToRefresh = function() { return this.each(function() { pullToRefresh(this); }); } $.fn.pullToRefreshDone = function() { return this.each(function() { pullToRefreshDone(this); }); }}($);
4.缓存数据
目前存储数据有几种,常见的是Cookies,而在H5中有sessionStorage和localStorage,顾名思义,前者的生命周期和session差不多,也就是当前窗口或标签页,一旦关闭就会被清除数据,而后者则是一直保存下去,需要主动清除才行。同时localStorage在Android中默认是关闭的,需要添加如下代码启用。
webSettings.setDomStorageEnabled(true);
感觉是不是sessionStorage比较省事,那么为什么最终选择了localStorage?
其实笔者一开始在项目中使用的是sessionStorage,但却遇到问题。项目是嵌入到其他app应用中的,有时需要调用原生页面(如摄像头的页面),然后再返回。项目本身是作为一个webview存在,这时sessionStorage会失效,所以选择了localStorage。
localStorage的使用方法和java的hashmap有点相似,但值得注意的是只能保存字符串,直接保存对象也只会是[object Object]
,笔者一般都是将对象(或数组)先通过JSON.stringify转换为字符串,然后使用的时候再通过JSON.parse转为对象使用的。sessionStorage和localStorage的方法一样,常用的有:
//设置一个键值 localStorage.setItem("second_list",""); //获取一个键值 localStorage.getItem("second_list"); //删除一个键值 localStorage.removeItem("second_list")//获取指定下标的键的名称(如同Array) localStorage.key(0); //清空localStorage.clear();
5.跳转与返回
笔者的项目是嵌入到原生APP应用中的,在Android中打开后会启动webview(目前也就理解是个和浏览器差不多的)加载,所以网页间的跳转和返回也应该像在浏览器上的一样,跳转可以通过location.href='new.html'
,而返回可以通过H5提供的History API,即history.back()
或history.go()
。
笔者一开始是处理返回(在页面上的点击返回按钮)是直接打开新页面的,location.href='pre.html'
。这会导致一个问题,当(在Android中)使用物理按键返回的时候,原生APP是根据历史纪录返回的,这样会返回到其他的页面。举个例子,打开APP进入到webview中,现在在A页面中,打开了B页面,然后点击返回。这时webview的历史记录是A->B->A,而不是理想中只有的A,这种情况下在A按下物理按键的时候,本应该是直接退出的,但是会出现先返回到B,再返回到A才能退出的情况,这样是不能接受的。所以就需要使用History API。
当然也可以监听物理按键(Android可以,iOS好像不行)处理,不过感觉这样不友好,而且麻烦。
if (window.history && window.history.pushState) { $(window).on('popstate', function () { location.href='pre.html'; }); }
6.输入与虚拟键盘
这里不得不提的是contenteditable
这个属性,真的是太棒了。元素添加这个就会变成可自动调整高度的文本框。值得注意的是,如果是在flex布局中使用,需要添加word-break: break-all;
才能实现自动换行。
当点击input
或者是含有contenteditable
属性的元素时,手机会自动弹出虚拟键盘,这时如果有元素是fixed且在底部的时候,需要输入的元素会被挡住,或者是跑到奇怪的位置上去,这时可以通过监听window是否变小,来判断虚拟键盘是否弹出,然后隐藏fixed的元素,变大则显示。
var h = $(window).height(); $(window).resize(function (){ if( $(window).height()<h ){ $('.footer_btn_panel').hide(); $('.footerButton').hide(); }else{ $('.footer_btn_panel').show(); $('.footerButton').show(); } });
H5也提供了scrollIntoView实现该效果。
var element = document.getElementById("sb_form_q");element.scrollIntoView(false);
使用虚拟键盘的搜索,需要通过form来实现。
<form action="#"> <input type="search" /></form>
$('form').on('submit', function(){ //search document.activeElemnt.blur(); return false;});
- 移动端H5填坑指南
- 【HADOOP】“填坑”指南
- 移动H5前端性能优化指南
- 移动H5前端性能优化指南
- 移动H5前端性能优化指南
- 移动H5前端性能优化指南
- 移动H5前端性能优化指南
- 移动H5前端性能优化指南
- 移动H5前端性能优化指南
- 移动H5前端性能优化指南
- 移动H5前端性能优化指南
- 移动H5前端性能优化指南
- 移动H5前端性能优化指南
- 移动H5前端性能优化指南
- 移动H5前端性能优化指南
- 移动H5前端性能优化指南
- 移动H5前端性能优化指南
- 移动H5前端性能优化指南
- 信息检索导论笔记之1--3章
- Mapreduce与Yarn 原理分析
- (十七)文件类
- 安卓开发——如何判断软键盘是否弹出(显示)
- hiho182
- 移动端H5填坑指南
- n个数里最小的k个
- mysql优化之表的设计
- 【软工】黑白盒
- Linux到底是什么,它是怎么诞生的,它的诞生有怎样的意义!
- 数据结构实验之排序六:希尔排序
- Android中涉及到的Groovy语法
- Web前端之HTML
- [kuangbin带你飞]专题1 简单搜索 H