移动端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">                &nbsp;            </div>            <div class="ground ground-blue">                &nbsp;            </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;});