移动端H5填坑指南

移动端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">item1div>
  <div class="item">item2div>
  <div class="item">item3div>
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()'.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;
});

你可能感兴趣的:(前端)