移动端重构

系列1——前期准备工作

本系列文章,如果没有特别说明,兼容安卓4.0.4+

pc端响应式开发

火狐浏览器

打开菜单栏,点击开发者,选择我们的响应式设计

firefox 响应式

谷歌浏览器

点击“show console”图标,打开模拟面板,使用"User Agent",选择所要模拟的机子,点击emulate按钮模拟,刷新浏览器。如要调整横竖屏,切换到"screen"

chrome 响应式

二维码扫描

火狐中文版,地址栏右侧自带二维码扫描功能

firefox 二维码

谷歌浏览器,可以安装“二维码qr码生成器”这个插件

chrome 二维码

调试

关于这个研究不多,在这推荐几篇文章吧(因为平时写css比较多,调试什么的直接pc端就可以解决了,一看效果不对,大概就知道什么问题了,换个思路或结构什么的,所以一般使用最多的是二维码和ip地址访问)

  •  通过Mac远程调试iPhone/iPad上的网页
  •  Android上的远程调试
  •  Android 设备 Chrome 远程调试
  •  uc浏览器开发者调试







  • 系列2——新建空白页面

    html5文档申明

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
    </head>
    <body>
    
    </body>
    </html>
    

    meta标签

    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, minimal-ui" />
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-status-bar-style" content="black" />
    <meta name="format-detection"content="telephone=no, email=no" />
    

    viewport

    视图窗口,移动端特属的标签。一般使用下面这段代码即可:

    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, minimal-ui" />
    

    上面的代码依次表示设置宽度为设备的宽度,默认不缩放,不允许用户缩放(即禁止缩放),在网页加载时隐藏地址栏与导航栏(ios7.1新增)。

    width – // [pixel_value | device-width] viewport 的宽度,范围从 200 到 10,000,默认为 980 像素
    height – // [pixel_value | device-height ] viewport 的高度,范围从 223 到 10,000 
    initial-scale – // float_value,初始的缩放比例 (范围从 > 0 到 10)
    minimum-scale – // float_value,允许用户缩放到的最小比例
    maximum-scale – // float_value,允许用户缩放到的最大比例
    user-scalable – // [yes | no] 用户是否可以手动缩放
    target-densitydpi = [dpi_value | device-dpi | high-dpi | medium-dpi | low-dpi] 目标屏幕像素密度
    

    注:target-densitydpi屏幕像素密度和缩放有关,你可以试试修改这个demo,用手机看下实际效果。我一般不设置这个属性。

    apple-mobile-web-app-capable

    是否启动webapp功能,会删除默认的苹果工具栏和菜单栏。

    <meta name="apple-mobile-web-app-capable" content="yes" />
    

    apple-mobile-web-app-status-bar-style

    当启动webapp功能时,显示手机信号、时间、电池的顶部导航栏的颜色。默认值为default(白色),可以定为black(黑色)和black-translucent(灰色半透明)。这个主要是根据实际的页面设计的主体色为搭配来进行设置。

    <meta name="apple-mobile-web-app-status-bar-style" content="black" />
    

    注:如果对apple-mobile-web-app-capableapple-mobile-web-app-status-bar-style不太理解,可查阅下面的参考资料第三篇文章,里面有截图说明。

    telephone & email

    忽略页面中的数字识别为电话号码

    <meta name="format-detection" content="telephone=no" />
    

    同样还有一个email识别

    <meta name="format-detection" content="email=no" />
    

    当然两者可以写在一起

    <meta name="format-detection" content="telphone=no, email=no" />
    

    其他meta

    <!-- 启用360浏览器的极速模式(webkit) -->
    <meta name="renderer" content="webkit">
    <!-- 避免IE使用兼容模式 -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <!-- 针对手持设备优化,主要是针对一些老的不识别viewport的浏览器,比如黑莓 -->
    <meta name="HandheldFriendly" content="true">
    <!-- 微软的老式浏览器 -->
    <meta name="MobileOptimized" content="320">
    <!-- uc强制竖屏 -->
    <meta name="screen-orientation" content="portrait">
    <!-- QQ强制竖屏 -->
    <meta name="x5-orientation" content="portrait">
    <!-- UC强制全屏 -->
    <meta name="full-screen" content="yes">
    <!-- QQ强制全屏 -->
    <meta name="x5-fullscreen" content="true">
    <!-- UC应用模式 -->
    <meta name="browsermode" content="application">
    <!-- QQ应用模式 -->
    <meta name="x5-page-mode" content="app">
    <!-- windows phone 点击无高光 -->
    <meta name="msapplication-tap-highlight" content="no">
    

    参考资料:

    link标签

    apple-touch-icon

    如果apple-mobile-web-app-capable设置为yes了,那么在iPhone,iPad,iTouch的safari上可以使用添加到主屏按钮将网站添加到主屏幕上。而通过设置相应apple-touch-icon标签,则添加到主屏上的图标就会使用我们指定的图片。

    以下是针对ox不同设备,选择一个最优icon。默认iphone的大小为60px,ipad为76px,retina屏乘以2倍。

    <link rel="apple-touch-icon" href="touch-icon-iphone.png">
    <link rel="apple-touch-icon" sizes="76x76" href="touch-icon-ipad.png">
    <link rel="apple-touch-icon" sizes="120x120" href="touch-icon-iphone-retina.png">
    <link rel="apple-touch-icon" sizes="152x152" href="touch-icon-ipad-retina.png">
    

    ios7以前系统默认会对图标添加特效(圆角及高光),如果不希望系统添加特效,则可以用apple-touch-icon-precomposed.png代替apple-touch-icon.png

    图标使用的优先级如下:

    •  如果没有跟相应设备推荐尺寸一致的图标,那个会优先使用比推荐尺寸大,但最接近推荐尺寸的图标。
    •  如果没有比推荐尺寸大的图标,会优先选择最接近推荐尺寸的图标。
    •  如些有多个图标符合推荐尺寸,会优先选择包含关键字precomposed的图标。

    如果未在区域指定用link标签指定图标,会自动搜索网站根目录下以apple-touch-icon为前缀的png图标。

    注:ios7不再为icon添加特效,ios7以前则默认为icon添加特效,除非icon有关键字-precomposed.png为后缀。

    参考资料:

    apple-touch-startup-image

    同样基于apple-mobile-web-app-capable设置为yes,可以用WebApp设置一个类似NativeApp的启动画面。

    <link rel="apple-touch-startup-image" href="/startup.png">
    

    apple-touch-icon不同,apple-mobile-web-app-capable不支持sizes属性,所以使用media来控制retina和横竖屏加载不同的启动画面。

    // iPhone
    <link href="apple-touch-startup-image-320x460.png" media="(device-width: 320px)" rel="apple-touch-startup-image" />
    
    // iPhone Retina
    <link href="apple-touch-startup-image-640x920.png" media="(device-width: 320px) and (-webkit-device-pixel-ratio: 2)" rel="apple-touch-startup-image" />
    
    // iPhone 5
    <link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)" href="apple-touch-startup-image-640x1096.png">
    
    // iPad portrait
    <link href="apple-touch-startup-image-768x1004.png" media="(device-width: 768px) and (orientation: portrait)" rel="apple-touch-startup-image" />
    
    // iPad landscape
    <link href="apple-touch-startup-image-748x1024.png" media="(device-width: 768px) and (orientation: landscape)" rel="apple-touch-startup-image" />
    
    // iPad Retina portrait
    <link href="apple-touch-startup-image-1536x2008.png" media="(device-width: 1536px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 2)" rel="apple-touch-startup-image" />
    
    // iPad Retina landscape
    <link href="apple-touch-startup-image-1496x2048.png"media="(device-width: 1536px)  and (orientation: landscape) and (-webkit-device-pixel-ratio: 2)"rel="apple-touch-startup-image" />
    

    参考资料:

    总结

    空白页面模板,然后再根据具体情况在此基础上添加apple-touch-icon和apple-touch-startup-image

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, minimal-ui" />
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta name="apple-mobile-web-app-status-bar-style" content="black" />
        <meta name="format-detection"content="telephone=no, email=no" />
        <title>Document</title>
    </head>
    <body>
    
    </body>
    </html>
    

    系列3——整体布局

    动端和pc端一样整体上布局也可以分为上中下三部分,如图:

    layout template

    一般来说,header和footer部分都为fixed定位,中间的内容区域可滚动。

    fixed布局

    常规的结构如下:

    <header class="header fixed-top"></header>
    <div class="wrap-page">
        <section class="page"></section>
        <section class="page"></section>
        ...
    </div>
    <footer class="footer fixed-bottom"></footer>
    

    因为移动端单页面特性,所以每个page为一个页面,然后整体使用wrap-page包裹。考虑到可滚动的为page内容,所以我们得给wrap-page一个具体的高度,然后使用原生的-webkit-overflow-scrolling:touch;来实现滚动,当然对于不支持的,也可以使用iscroll来兼容,而iscroll同样也需要一个固定高度的容器来包裹可滚动的内容。

    * {
      -webkit-box-sizing: border-box;
      -moz-box-sizing: border-box;
      box-sizing: border-box;
    }
    html, body, .wrap-page {
      height: 100%;
    }
    .wrap-page {
      -webkit-overflow-scrolling: touch;
    }
    .fixed-top {
      position: fixed;
      left: 0;
      right: 0;
      top: 0;
      z-index: 960;
    }
    
    .fixed-bottom {
      position: fixed;
      left: 0;
      right: 0;
      bottom: 0;
      z-index: 940;
    }
    

    虽然header和footer部分采用了fixed定位,脱离了文档流,但还是会挡住下面的内容,所以有必要对wrap-page设置上下的padding间隔,以防止header和footer遮挡page内容(假设header和footer高度各为44px):

    .wrap-page {
      padding-top: 44px;
      padding-bottom: 44px;
    }
    

    看起来不错,不过如果碰到有些页面有header和footer,而有些页面只有header,而有些甚至有两个header部分,这么一刀切的方法显然不合适。按照这个结构只好通过js来给wrap-page添加不同的class来设置上下的padding。下面我们说下另一种用css就能解决的方法。

    优化结构

    这次我们把footer提到wrap-page上面,然后采用兄弟选择器,就可以解决刚才那个padding问题,ratchet就是采用该方法(经反馈,一些国产的安卓机对兄弟元素选择器支持不太好,所以这个方案适合高上大的玩,更新时间:2014-07-03)

    <header class="header fixed-top"></header>
    <footer class="footer fixed-bottom"></footer>
    <div class="wrap-page">
        <section class="page"></section>
        <section class="page"></section>
        ...
    </div>
    

    这样我们就可以采用兄弟选择器,设置上下的padding:

    .header ~ .wrap-page {
      padding-top: 44px;
    }
    .footer ~ .wrap-page {
      padding-bottom: 44px;
    }
    

    同样如果有二层header部分(如搜索框):

    <header class="header fixed-top"></header>
    <section class="header-sub"></section>
    <footer class="footer fixed-bottom"></footer>
    <div class="wrap-page">
        <section class="page"></section>
        <section class="page"></section>
        ...
    </div>
    

    还是采用兄弟选择器,将wrap-page的padding-top设置为header的高度加上header-sub的高度:

    .header-sub ~ .wrap-page {
      padding-top: 88px;
    }
    

    看起来不错,再也不用通过js来判断对wrap-page增删class了。

    不过这个也同样有个问题,为了说明这个问题,我们还是回到移动端单页面特性这个上面,我们的页面是通过page的形式添加到wrap-page这个包裹上的,每次只显示一个而已。

    <div class="wrap-page">
        <section class="page"></section>
        <section class="page" style="display:none;"></section>
        <section class="page" style="display:none;"></section>
        ...
    </div>
    

    当然每个页面不可能只有content部分,也会有header和footer,不同页面存在不同的header或footer这是绝对可能的。那么如果header和footer部分也通过这种显示隐藏的方式来搞呢?

    如:我们从一个有header和footer的页面,切换到一个只有header的页面,且header改变,就会成为下面这样:

    <header class="header fixed-top" style="display:none;"></header>
    <header class="header fixed-top"></header>
    <footer class="footer fixed-bottom" style="display:none;"></footer>
    

    这样虽然footer隐藏了,但是对于下面这条样式同样还是会解析生效,wrap-page会有44px的padding-bottom

    .footer ~ .wrap-page {
      padding-bottom: 44px;
    }
    

    所以如果我们采用这种布局,header和footer绝对不能采用显示显示隐藏的方式来搞,而应该采用替换形式,没有则删除。具体可以参考ratchet的实现方式

    绝对定位布局

    直接参考demo,关键在于设置wrap-page的top,bottom的距离为header和footer的高度。

    css代码如下:

    .header,.footer,.wrap-page{
      position:absolute;
      left:0;
      right:0;
    }
    .header,.footer{
      height:44px;
      background-color: #fff;
      text-align: center;
      z-index:900;
      line-height:44px;
    }
    .header{
      top: 0;
      border-bottom: 1px solid #f00;
    }
    .footer{
      bottom: 0;
      border-top: 1px solid #f00;
    }
    .wrap-page{
      top: 44px;
      bottom: 44px;
      overflow-y:auto;
      -webkit-overflow-scrolling:touch;
    }
    .page{
      padding: 10px;
    }
    .page p{
      margin-bottom: 10px;
    }

    这个布局的缺陷在于滚动的时候地址栏不隐藏,safari浏览器可以通过下面js代码来隐藏地址栏,其他浏览器经测试不可以

    window.addEventListener('load', function(){
       setTimeout(function(){ window.scrollTo(0, 1); }, 100);
    });
    

    如果你实在要除掉浏览器的地址栏和工具栏,可以设置meta标签为应用模式,参考新建空白页面的其他meta部分

    <!-- UC应用模式 -->
    <meta name="browsermode" content="application">
    <!-- QQ应用模式 -->
    <meta name="x5-page-mode" content="app">
    

    flex布局

    可以通过这个简单的demo来测试:flex layout demo

    设置body为flex布局,方向为垂直方向,wrap-page的flex为1。这个跟上面的绝对定位一样,还是滚动的时候地址栏不隐藏,safari同样可以通过js来搞定,其他浏览器不可以

    body {
      display: -webkit-box;
      display: -ms-flexbox;
      display: -webkit-flex;
      display: flex;
      -webkit-box-orient: vertical;
      -ms-flex-direction: column;
      -webkit-flex-direction: column;
      flex-direction: column;
    }
    
    .wrap-page {
      -webkit-box-flex: 1;
      -ms-flex: 1;
      -webkit-flex: 1;
      flex: 1;
    }
    
    .header,.footer{
      height:44px;
      background-color: #fff;
      text-align: center;
      line-height:44px;
      position:relative;
      z-index:990;
    }
    .header{
      border-bottom: 1px solid #f00;
    }
    .footer{
      border-top: 1px solid #f00;
    }
    .wrap-page{
      overflow-y:auto;
      -webkit-overflow-scrolling:touch;
    }
    .page{
      padding: 10px;
    }
    .page p{
      margin-bottom: 10px;
    }

    总结

    因为fixed定位,滚动的时候bug太多,特别是有表单元素的时候得慎用;而flex布局兼容方面有一定问题,好像性能也不是很好,况且如果是在body下面直接布局的话,只有上中下这几个元素还好,如果再添加上弹窗,panel什么的子元素搞不好还有问题得深入;所以选择绝对定位相对来说还是比较靠谱的。而优化的元素位置关系,因为国产的安卓手机太多,有些还不太支持,再加上隐藏的元素选择器还有效,所以暂时不考虑。

    最后我们一般采用常规结构的绝对定位来布局。


    系列4——重置样式

    基于Normalize.css,根据目前我们大家的使用习惯进行了一些清零及移动端的特点添加一些基础样式。
  • html { font-family: "Helvetica Neue", Helvetica, STHeiTi, Arial, sans-serif; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; font-size: 62.5%; }
    body { margin: 0; font-size: 1.4rem; line-height: 1.5; color: #333333; background-color: white; height: 100%; overflow-x: hidden; -webkit-overflow-scrolling: touch; }
    
    article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section, summary { display: block; }
    audio, canvas, progress, video { display: inline-block; vertical-align: baseline; }
    audio:not([controls]) { display: none; height: 0; }
    [hidden], template { display: none; }
    svg:not(:root) { overflow: hidden; }
    
    a { background: transparent; text-decoration: none; -webkit-tap-highlight-color: transparent; color: #0088cc; }
    a:active { outline: 0; }
    a:active { color: #006699; }
    abbr[title] { border-bottom: 1px dotted; }
    b, strong { font-weight: bold; }
    dfn { font-style: italic; }
    mark { background: #ff0; color: #000; }
    small { font-size: 80%; }
    sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
    sup { top: -0.5em; }
    sub { bottom: -0.25em; }
    img { border: 0; vertical-align: middle; }
    hr { -moz-box-sizing: content-box; box-sizing: content-box; height: 0; }
    pre { overflow: auto; white-space: pre; white-space: pre-wrap; word-wrap: break-word; }
    code, kbd, pre, samp { font-family: monospace, monospace; font-size: 1em; }
    
    button, input, optgroup, select, textarea { color: inherit; font: inherit; margin: 0; }
    button { overflow: visible; }
    button, select { text-transform: none; }
    button, html input[type="button"], input[type="reset"], input[type="submit"] { -webkit-appearance: button; cursor: pointer; }
    button[disabled], html input[disabled] { cursor: default; }
    button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; }
    input { line-height: normal; }
    input[type="checkbox"], input[type="radio"] { box-sizing: border-box; padding: 0; }
    input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { height: auto; }
    input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; }
    input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; }
    fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; }
    legend { border: 0; padding: 0; }
    textarea { overflow: auto; resize: vertical; }
    optgroup { font-weight: bold; }
    
    table { border-collapse: collapse; border-spacing: 0; }
    td, th { padding: 0; }
    
    html, button, input, select, textarea { font-family: "Helvetica Neue", Helvetica, STHeiTi, Arial, sans-serif; }
    h1, h2, h3, h4, h5, h6, p, figure, form, blockquote { margin: 0; }
    ul, ol, li, dl, dd { margin: 0; padding: 0; }
    ul, ol { list-style: none outside none; }
    h1, h2, h3 { line-height: 2; font-weight: normal; }
    h1 { font-size: 1.8rem; }
    h2 { font-size: 1.6rem; }
    h3 { font-size: 1.4rem; }
    input::-moz-placeholder, textarea::-moz-placeholder { color: #cccccc; }
    input:-ms-input-placeholder, textarea:-ms-input-placeholder { color: #cccccc; }
    input::-webkit-input-placeholder, textarea::-webkit-input-placeholder { color: #cccccc; }
    
    * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; }
    

    具体说明请参考sandal中的reset文件

    这里有两点需要说明:

    1、关于moz前缀

    因为本人习惯使用firefox浏览器,所以没有舍弃moz前缀,而且moz前缀也不多,就几个而已,所以没有去掉。如有代码洁癖者,可去掉。

    2、关于字体

    font-family: "Helvetica Neue", Helvetica, STHeiTi, Arial, sans-serif;
    

    IOS

    iOS 4.0+ 使用英文字体 Helvetica Neue,之前的iOS版本降级使用 Helvetica,中文字体设置为华文黑体STHeiTi。

    Android

    原生Android下中文字体与英文字体都选择默认的无衬线字体,4.0之前版本英文字体原生Android使用的是Droid Sans,中文字体原生Android会命中Droid Sans Fallback,4.0+ 中英文字体都会使用原生Android新的Roboto字体。其他第三方Android系统也一致选择默认的无衬线字体。

    最后加上Arial字体方便pc端查看效果。

    参考资料:字体设置

    系列5——等分,居中等

    之所以把本篇单独拿出来讲解,是因为这些在移动端使用的频率太高了,然后实现方法也不尽相同,而这里主要说下如何用flex和translate来实现。
  • 注:代码部分涉及到sass的mixin部分,在sandal的mixin文件中均有定义,可以直接使用。

    等分

    在说等分之前,先抛出一个问题,如下面的emmet代码,footer部分的导航有些页面是三个,有些页面是四个,我们要求的是不论是三个还是四个甚至于5个,都平分宽度。

    footer.footer>ul.nav-links>li*3
    footer.footer>ul.nav-links>li*4
    

    float

    如果采用float技术的话,那估计只有在ul上添加额外的class来设置li的百分比宽度了。

    .nav-links li{
        float:left;
        width:25%;
    }
    .percent-half li{
        width:50%;
    }
    .percent-third li{
        width:33.333%;
    }
    ...
    

    这个太蛋疼了,高上大的移动端怎么能用这么老套的东西呢,所以不考虑。

    table

    也许这个技术会被很多人忘记,不过用在移动端确实不错,关键是没有兼容问题的。主要设置父元素的display: table;table-layout: fixed;width: 100%;,然后设置子元素为display: table-cell;即可。

    // table 等分
    @mixin table-equal($children: li) {
        display: table;
        table-layout: fixed;
        width: 100%;
        $childrenEle: li div p a span strong;
        @if index($childrenEle, $children) {
            #{$children} {
                display: table-cell;
            }
        }
        @else {
            .#{$children} {
                display: table-cell;
            }
        }
    }
    
    .nav-links{
        @include table-equal;
    }
    

    这个mixin内部定义了一个$childrenEle元素选择器变量集合,如果传入的参数是其中的一个,那么直接使用元素选择器,否则就当class选择器使用,如默认的li解析后就是li{display: table-cell;},而如果传入children,则解析后就是.children{display: table-cell;}。下面的flex同样使用了该方法

    注:在移动端display: table;同样也是个有利的神器,比起各种float什么的,这个技术还是可以单刀直入,直指问题核心

    flex

    flex技术是个好技术,不过最关键的还是其兼容问题,算起来它有三个版本,是有点乱哈哈。不过sandal的css3文件已经封装好了,所以只管调用,它会自动生成对应的兼容代码。

    // flex 等分
    @mixin flex-equal($children: li) {
        @extend %display-flex;
        $childrenEle: li div p a span strong;
        @if index($childrenEle, $children) {
            #{$children} {
                @include flex(1);
            }
        }
        @else {
            .#{$children} {
                @include flex(1);
            }
        }
    }
    
    .nav-links{
        @include flex-equal;
    }
    

    等分,居中等demo测试

    水平垂直居中

    以简单的弹窗为例:

    <div class="overlay">
        <section class="modal">
            <div class="modal-bd">
                <p>青,取之于蓝,而青于蓝;冰,水为之,而寒于水。故木受绳则直,金就砺则利,君子博学而日参省乎己,则知明而行无过矣。</p>
            </div>
        </section>      
    </div>
    

    也许看到这个结构,很多人都会纳闷,因为大家看到更多的应该是:.overlay+section.modal,即蒙版与弹出内容是兄弟元素,而不是嵌套关系。这里先卖个关子,到modal实例的时候,再分析。

    flex

    样式写在父元素上

    // flex center
    // display:flex
    %display-flex,%flex-display {
        @include display-flex;
    }
    
    @mixin flex-center($direction: both) {
        @extend %display-flex;
        @if $direction == both {
            @include justify-content(center);
            @include align-items(center);
        }
        @else if $direction == x {
            @include justify-content(center);
        }
        @else if $direction == y {
            @include align-items(center);
        }
    }
    
    .overlay{
        z-index: 980;
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        background-color: rgba(0,0,0,.8);
        @include flex-center; // overlay调用
    }
    .modal{
        background-color: #fff;
        border-radius: 5px;
        margin: 0 10px;
        overflow: hidden;
    
        .modal-bd{
            padding: 15px;
        }
    }
    

    关于flex的单个元素水平垂直居中,新语法直接父元素为flex,子元素设置margin为auto即可,因为移动端还在使用旧语法,所以暂不使用margin这个方法,而是设置父元素的水平及垂直都居中

    translate

    样式写在要居中的元素上。原理就是先绝对定位,left/top为50%,然后通过translate偏移-50%回去(translate偏移的百分比为自身宽高的百分比),比从前的margin-top/left设置负值偏移回去高级点,因为设置margin必须得知道自身元素的宽高,然后设置具体的数字,而translate不用管自身的宽高,直接50%就可以搞定

    // translate 50%
    @mixin translate-center($direction: both) {
        position: absolute;
        @if $direction == both {
            top: 50%;
            left: 50%;
            @include translate(-50%, -50%);
        }
        @else if $direction == x {
            left: 50%;
            @include translate(-50%, 0);
        }
        @else if $direction == y {
            top: 50%;
            @include translate(0, -50%);
        }
    }
    
    .overlay{
        z-index: 980;
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        background-color: rgba(0,0,0,.8);
    }
    .modal{
        @include translate-center;  // modal调用
        background-color: #fff;
        border-radius: 5px;
        width:300px;
        overflow: hidden;
    
        .modal-bd{
            padding: 15px;
        }
    }
    

    上面的flex和translate两个mixin都可以实现单独的水平居中或垂直居中,传入相应的x或y即可。不论是flex还是translate水平垂直居中都有两个很好的优势即无需借助额外的空标签,也无需知道子元素的具体宽高,这比从前的一些方法强多了

    等分,居中等demo测试

    左右两端对齐

    对于左右两端对齐,以前使用最多的可能就是float,position了,现在同样可以采用flex来搞定

    // justify
    @mixin justify($extend: true) {
        @if $extend {
            @extend %justify;
        }
        @else {
            @extend %display-flex;
            @include justify-content(space-between);
        }
    }
    %justify {
        @include justify(false);
    }
    
    .justify{
        @include justify;
    }
    

    等分,居中等demo测试

    总结

    如果你开始做移动端,那么flex和transform这两大属性有必要熟练运用,运用好了能解决很多问题。一般来说flex可以用来实现一些布局,再也不用动不动就float了;而transform中的rotate及translate则可以实现一些旋转及位移移动,旋转可以搞定图标的一些变化,而位移移动则可以实现居中,位移动画等。

    系列6——切入切出动画

    因为后面的几篇文章都需要用到切入切出动画什么的,所以先把这个说下。为了简单起见,我们这里只讨论translate偏移动画(translate比起绝对定位的top/left/right/bottom要高效),而如其他的旋转缩放淡入淡出什么的道理都一样。
  • transition动画

    先定义要运动的元素在视觉范围之外,以左方向进入为例,同时定义transition:

    .demo{
        @include translate3D(-2000px, 0, 0);
        -webkit-transition: -webkit-transform 0.3s ease-in-out;
        transition: transform 0.3s ease-in-out;
    }
    

    从进入视觉范围来说,不论方向从上下还是左右,最终都归于0,所以进入的时候添加class translate-in,而离开的时候去掉translate-in即可

    .translate-in{
        @include translate3D(0, 0, 0);
    }
    

    animation动画

    先定义要运动的元素在视觉范围之外,同样以左方向为例:

    .demo{
        @include translate3D(-2000px, 0, 0);
    }
    

    再定义keyframes:

    // 从左向右方向进入动画
    @mixin left-in($startX: -2000px, $endX: 0) {
        @include keyframes(left-in) {
            0% {
                @include translate3d($startX, 0, 0);
            }
            100% {
                @include translate3d($endX, 0, 0);
            }
        }
        .left-in {
            @include animation-name(left-in);
            @extend %animated;
        }
    }
    
    // 从右向左方向消失动画
    @mixin left-out($startX: 0, $endX: -2000px) {
        @include keyframes(left-out) {
            0% {
                @include translate3d($startX, 0, 0);
            }
            100% {
                @include translate3d($endX, 0, 0);
            }
        }
        .left-out {
            @include animation-name(left-out);
            @extend %animated;
        }
    }
    

    调用上面定义的keyframes,元素进入视觉范围添加class left-in,元素离开视觉范围则替换left-inleft-out,动画结束后调用animationend事件,删除left-out

    @include left-in;
    @include left-out;
    

    解析后的css为:

    .left-in, .left-out {
      -webkit-animation-duration: 1s;
      animation-duration: 1s;
      -webkit-animation-fill-mode: both;
      animation-fill-mode: both;
    }
    
    @-webkit-keyframes left-in {
      0% {
        -webkit-transform: translate3d(-2000px, 0, 0);
      }
    
      100% {
        -webkit-transform: translate3d(0, 0, 0);
      }
    }
    @keyframes left-in {
      0% {
        transform: translate3d(-2000px, 0, 0);
      }
    
      100% {
        transform: translate3d(0, 0, 0);
      }
    }
    .left-in {
      -webkit-animation-name: left-in;
      animation-name: left-in;
    }
    
    @-webkit-keyframes left-out {
      0% {
        -webkit-transform: translate3d(0, 0, 0);
      }
    
      100% {
        -webkit-transform: translate3d(-2000px, 0, 0);
      }
    }
    @keyframes left-out {
      0% {
        transform: translate3d(0, 0, 0);
      }
    
      100% {
        transform: translate3d(-2000px, 0, 0);
      }
    }
    .left-out {
      -webkit-animation-name: left-out;
      animation-name: left-out;
    }
    

    总结

    transition动画与animation动画的区别在于:

    1、transition动画只能定义开始和结束位置,中间无法定义;而keyframes则可以定义n帧作为中间的过渡帧。

    2、对于切入切出动画来说,transition动画我们只需添加删除一个class即可完成,而animation动画则需要切换两个class,再在最后删除class,比较复杂。

    3、如果你的动画不需要定制中间帧,那直接使用transition动画即可,切换一个class就可以了,运动结束时候可以js调用transitionend函数,而如果需要定制中间帧,那么还是animation,当然animation的事件有三个animationstart,animationiteration,animationend

    系列7——图标

    这里我们把图标分为三种:背景图片,直接绘制,@font-face。如无特殊情况,图标的标签采用i标签
  • 背景图片

    首先我们会选择sprite形式,把所有的图标都放在一个大图中,然后考虑到retina屏,所以我们的图标应该设计为实际大小的2倍,然后设置background-size为实际大小。以下面的msg icon为例:

    icon msg

    图中的每个icon大小为24px,实际应用时,我们是以12px来使用的:

    %icon-msg{
        display: inline-block;
        vertical-align: -2px;
        background:url(../images/icon-msg.png) no-repeat;
        background-size:26px 26px; // 整个sprite图片大小的一半,注意不要采用50%,百分比是按元素大小来计算的,而不是背景图片大小
    }
    .icon-info{
        @extend %icon-msg;
        background-position: -14px 0;
        width: 12px;
        height: 12px;
    }
    .icon-alert{
        @extend %icon-msg;
        background-position: 0 -14px;
        width: 12px;
        height: 12px;
    }
    ...
    

    当然有时候图标比较少,我们为了减少请求,也可以直接把图片转成base64格式写在css中,这里推荐一个在线转的工具:Encode Data URL

    直接绘制

    凭借优秀的css3,我们可以应用其中一些属性绘制一些简单的图标,如箭头等,这里我们以绘制checkbox两种状态为例:

    icon checkbox

    html:

    <i class="icon-checkbox active"></i>    
    <i class="icon-checkbox"></i>
    

    scss:

    $primary:                        #0078e7 !default;
    
    .icon-checkbox{
        width: 16px;
        height: 16px;
        display: inline-block;
        vertical-align: middle;
        border: 1px solid #ccc;
        background-color: #fff;
        line-height: 1;
        text-align: center;
        margin-right: 5px;
    
        &.active{
            border-color: $primary;
    
            &::after{
                content: "";
                width: 8px;
                height: 3px;
                border-bottom: 2px solid $primary;
                border-left: 2px solid $primary;
                display: block;
                margin-top: 3px;
                margin-left: 2px;
                @include rotate(-45deg);
            }   
        }
    }
    

    active状态,通过after生成一个长方形,然后设置其border-bottom和border-left,再通过css3的rotate旋转45即可,那个勾就是两条边框。

    @font-face

    sandal的字体图标为例,如果你觉得这些图标不适合你,你可以自己在icomoon中挑选合适的。

    icons

    sandal中字体图标使用方法:

    1、下载sandal放在d盘目录下,在你的scss文件中导入sandal的base文件(如果不需要生成样式,则导入function文件即可)及font-face文件

    @import "d:/sandal/base";
    @import "d:/sandal/ext/font-face/font-face";
    

    2、根据自己需要覆盖font-face文件夹中的变量,注意变量应该在导入font-face之前,可以覆盖的变量如下:

    $fontFamily:            icomoon !default;
    $fontFilePath:          "../fonts/icomoon" !default;
    $fontClassPrefix:       if !default; // icon-font
    $fontClassAllSwitch:    true !default;
    $fontClassOutput:       () !default;
    $fontPseudo:            true !default; // 是否采用伪元素(before)生成图标
    

    下面我们改变下class的前缀,然后输出所有的字体class

    @import "d:/sandal/base";
    $fontClassPrefix:      icon-font;
    @import "d:/sandal/ext/font-face/font-face";
    

    3、把font-face目录下的fonts文件夹拷贝进解析后的css文件夹同目录下,如css,js,fonts,images同目录

    根据上面的配置,贴出下面的html和解析后的css代码:

    html:

    <i class="icon-font-wifi"></i>
    <i class="icon-font-comment"></i>
    <i class="icon-font-user"></i>
    <i class="icon-font-map"></i>
    ...
    

    css:

    .icon-font-wifi::before, .icon-font-comment::before, .icon-font-user::before, .icon-font-map::before,...{
      -webkit-box-sizing: border-box;
      box-sizing: border-box;
    }
    @font-face {
      font-family: icomoon;
      font-weight: normal;
      font-style: normal;
      src: url("../fonts/icomoon.eot");
      src: url("../fonts/icomoon.eot?#iefix") format("eot"), url("../fonts/icomoon.svg#icomoon") format("svg"), url("../fonts/icomoon.woff") format("woff"), url("../fonts/icomoon.ttf") format("truetype");
    }
    .icon-font-wifi::before, .icon-font-comment::before, .icon-font-user::before, .icon-font-map::before,...{
      display: inline-block;
      vertical-align: -2px;
      font-family: icomoon;
      font-size: 1.6rem;
      line-height: 1;
      speak: none;
      font-style: normal;
      font-weight: normal;
      font-variant: normal;
      text-transform: none;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
    }
    .icon-font-wifi::before {
      content: "\e62f";
    }
    .icon-font-comment::before {
      content: "\e601";
    }
    .icon-font-user::before {
      content: "\e632";
    }
    .icon-font-map::before {
      content: "\e61b";
    }
    ...
    

    一般我是直接绘制和字体图标都用,简单的直接绘制就好,所以为了区别两者的class,直接绘制的我使用icon为前缀,而字体图标使用if(icon-font缩写)为前缀,至于为什么要区别这两者的class呢,因为说不定你就得使用css3的属性选择器,比如i[class^="icon-"],i[class^="if-"]方便选择控制样式。

    关于变量$fontPseudo这里单独说明下,因为使用字体图标有两种方法,一种是把对应的字符编码直接写在html中,然后设置字体即可,另一种是html为空白标签,通过before或after的content来设置其内容,再设置字体。如果$fontPseudo为false,则解析的css为:

    @font-face {
      font-family: icomoon;
      font-weight: normal;
      font-style: normal;
      src: url("../fonts/icomoon.eot");
      src: url("../fonts/icomoon.eot?#iefix") format("eot"), url("../fonts/icomoon.svg#icomoon") format("svg"), url("../fonts/icomoon.woff") format("woff"), url("../fonts/icomoon.ttf") format("truetype");
    }
    .icon-font-wifi, .icon-font-comment, .icon-font-user, .icon-font-map,...{
      display: inline-block;
      vertical-align: -2px;
      font-family: icomoon;
      font-size: 1.6rem;
      line-height: 1;
      speak: none;
      font-style: normal;
      font-weight: normal;
      font-variant: normal;
      text-transform: none;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
    }
    

    注:个人只所以采用伪元素及把样式写在伪元素里面,是因为有些时候可能想偷懒,一些图标不直接采用一个空白标签去定义,而是直接写在某个元素的before或after伪元素上,那个时候只需要采用sass的extend对应图标的伪元素即可。

    系列8——滚动

    原生滚动

    原生滚动的属性为:-webkit-overflow-scrolling:touch;,如果是走高富帅的苹果路线,是没问题的,谁让这是人家亲生的呢;如果是安卓的话,我真不知道是支持还是不支持,说不支持吧好像有点缓动效果,说支持吧好像把这条属性砍掉也一样,感兴趣的可以自己测试下(添加或删除这个属性对比下)

    原生滚动支持测试:

    iscroll模拟

    既然原生的安卓上不太靠谱,那就有必要用js来解决了,因为本人不擅长js,所以直接上iscroll,现在iscroll也已经到第五版本了,iscroll的github上有很全的例子,虽然比较简单,不过入门还是不错的。这里我也搞了两个demo,在demo之前,有必要先说明下使用iscroll应该注意的一些事项:

    html结构方面

    iscroll要求至少两层结构,wrap是一个固定的容器,overflow为hidden,而scroll为滚动的内容,如果开启translate(默认开启),则使用translate来实现偏移滚动,如果没有则使用left/top来实现偏移滚动。如果wrap下面有多个直接子元素,即scroll有其他兄弟元素则只对第一个子元素滚动,其他的自动忽略。(wrap和scroll的class是随便的,主要就是得有两层结构)

    <div class="wrap">
        <div class="scroll">
            ...
        </div>
    </div>
    

    如图:

    iscoll img

    css样式方面

    除了要求wrap有宽高及overflow为hidden,还得对scroll元素设置position为relative或absolute,这样才能设置偏移。

    js方面

    最简单的就是直接new一个IScroll对象即可,其他配置可直接参考官网的说明:

    window.addEventListener('load',function(){ var myScroll = new IScroll('.wrap');});
    document.addEventListener('touchmove', function (e) { e.preventDefault(); }, false);
    

    献上两个简单的demo:

    由于本人的js水平实在有限,关于这个我也分析不出来什么来龙去脉,摘录几篇文章以供想学习的参考下吧。下面的资料都是iscroll 4的。(虽然官网已经介绍的足够好了,但iscroll的坑还是很多的,多看看总不赖)

    参考资料:

    系列9——图片滚动

    这里我们采用swipe来做我们移动端的图片滚动
  • 滚动原理

    既然说到滚动,那跟我们上篇说到的iscroll多少有点关系吧。下面对swipe的滚动原理和iscroll的滚动原理简单分析下,以相同的结构为例:

    <div class="wrap">
        <ul class="scroll">
            <li></li>
            <li></li>
            ...
        </ul>
    </div>
    

    相同的是都需要一个可以设置overflow为hidden的容器wrap。不同的是iscroll是对scroll元素进行滚动,通过设置其translate值或left/top值来实现滚动;而swipe是对li进行滚动,每个li有三种状态——prev,current和next,prev状态的时候向左偏移视窗的宽度,current状态正好在视觉范围内,next状态向右偏移视窗的宽度。

    实战

    同样其github已经有了很好的api使用说明,这里简单说下其参数配置:

    window.mySwipe = new Swipe(document.getElementById('slider'), {
      startSlide: 2, //起始图片切换的索引位置,默认为0
      speed: 400, //动画执行时间,单位毫秒,默认为300
      auto: 3000, //设置自动切换时间,单位毫秒,无默认值
      continuous: true, //无限循环的图片切换效果,默认为true
      disableScroll: false, //阻止由于触摸而滚动屏幕,默认为false
      stopPropagation: false, //阻止事件冒泡,默认为false
      callback: function(index, elem) {}, //回调函数,滚动时调用
      transitionEnd: function(index, elem) {} //回调函数,滚动的transition动画结束后调用。
    });
    

    除此之外,swipe还提供了几个比较实用的api,如下:

    •  prev()上一页
    •  next()下一页
    •  getPos()获取当前页的索引
    •  getNumSlides()获取所有项的个数
    •  slide(index, duration)滑动方法

    demo

    1、简单demo

    css代码如下,图片大小最大为100%宽度:

    .wrap{
      overflow: hidden;
      visibility: hidden;
      position: relative;
      width: 100%;
      height: 150px;
    }
    .wrap .inline-float{
      overflow: hidden;
      position: relative;
    }
    .wrap .inline-float li {
      float: left;
      width: 100%;
      position: relative;
      text-align: center;
    }
    .wrap .inline-float img {
      max-width: 100%;
    }
    

    2、多张图片一组demo

    即每个li中多放几张图片,然后控制图片的宽度,以防止挤掉

    .wrap .inline-float img {
      max-width: 30%;
      margin-left: 10px;
    }
    

    3、全屏图片

    这个关键在于把图片设置背景图片,然后采用background-size来调整,这里采用的是100%,即li的宽高大小(demo中的四张图片均不同大小,然后缩放统一呈现出来的大小就是li的大小),可以根据自己的需要设置为cover或contain什么的

    .wrap .inline-float li {
      float: left;
      width: 100%;
      height: 150px;
      position: relative;
      text-align: center;
      background-repeat:no-repeat;
      background-size: 100% 100%;
      background-color:#f5f5f5;
    }
    

    注:上面的几个demo好像是直接调用Swipe函数,而不是new一个对象,js修改为如下(jsbin中一修改链接数字就变了):

    window.addEventListener('DOMContentLoaded', function(){ 
      var $slide = document.getElementById('swipeSlide');
      var mySwipe = new Swipe($slide,{
        auto: 1000
      });
    });
    

    4、添加子弹导航

    js部分,设置callback函数,根据当前的位置为子弹导航添加激活状态

    window.addEventListener('DOMContentLoaded', function(){ 
      var $slide = document.getElementById('swipeSlide'),
          aBullet = $slide.querySelectorAll('.icon-bullet'),
          len = aBullet.length;
    
      var mySwipe = new Swipe($slide,{
        auto: 3000,
        callback: function(i){
          while(len--) {
            aBullet[len].classList.remove('active');
          }
          aBullet[i].classList.add('active');
        }
      });
    });

    系列10——侧边栏导航

    panel一般用来做侧边栏导航,铺满整屏高度,有两种展现形式:第一种直接盖在整块内容栏(包括header和footer部分)上面,如图一;第二种把整块内容栏推开panel的宽度,如图二

    panel 1panel 2

    设计结构如下:

    id="cproIframe_u1589922_1" width="480" height="60" src="http://pos.baidu.com/acom?adn=0&at=128&aurl=&cad=1&ccd=24&cec=UTF-8&cfv=17&ch=0&col=zh-CN&conOP=0&cpa=1&dai=1&dis=0&layout_filter=rank%2Cimage&ltr=http%3A%2F%2Fwww.w3cplus.com%2Fmobile%2Fmobile-terminal-refactoring-scroll-with-swipe.html&ltu=http%3A%2F%2Fwww.w3cplus.com%2Fmobile%2Fmobile-terminal-refactoring-sidebar-menu.html&lunum=6&n=01006029_cpr&pcs=1583x775&pis=10000x10000&ps=763x141&psr=1600x900&pss=1583x855&qn=d8827219bc79d371&rad=&rsi0=480&rsi1=60&rsi5=4&rss0=&rss1=&rss2=&rss3=&rss4=&rss5=&rss6=&rss7=&scale=&skin=tabcloud_skin_3&stid=5&td_id=1589922&ti=%E7%A7%BB%E5%8A%A8%E7%AB%AF%E9%87%8D%E6%9E%84%E7%B3%BB%E5%88%9710%E2%80%94%E2%80%94%E4%BE%A7%E8%BE%B9%E6%A0%8F%E5%AF%BC%E8%88%AA%20%7C%20css3%E6%95%99%E7%A8%8B-css3%E5%AE%9E%E4%BE%8B-css3%E5%8A%A8%E7%94%BB%20%7C%20W3CPlus&tn=baiduCustSTagLinkUnit&tpr=1428741440294&ts=1&version=2.0&xuanting=0&dtm=BAIDU_DUP2_SETJSONADSLOT&dc=2&di=u1589922&tt=1428741440265.30.103.105" align="center,center" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" allowtransparency="true" style="box-sizing: inherit;">
    <header class="header"></header>
    <div class="wrap-page">
        <section class="page"></section>
        ...
    </div>
    <footer class="footer"></footer>
    <section class="panel"></section>
    

    第一种实现方案:

    demo 1

    先将panel通过translate偏移负的本身宽度,离开可视区域,然后通过切换active这个class来实现无偏移。当然除此之外,top和bottom的0实现了100%高度,z-index要保证大于header和footer的层级。

    $panelWidth:      120px !default;
    .panel{
        position: absolute;
        top: 0;
        bottom: 0;
        left: 0;
        z-index: 980;
        width: $panelWidth;
        background-color: #333;
        @include translate3d(-$panelWidth, 0, 0);
        @extend %transition-transform;
    }
    .panel.active{
        @include translate3d(0, 0, 0);
    }
    

    同样我们也可以通过给body添加删除class如panel-active来控制panel的位置。

    第二种实现方案

    demo 2,在demo1的基础上根据第二种方案顺便处理下了当panel出现时,内容禁止滚动

    因为需要实现整块内容栏(包括header和footer部分)偏移panel的宽度,所以第一反应是应该有个div把整块内容栏包裹下,如下:

    <div class="wrap-container">
        <header class="header"></header>
        <div class="wrap-page">
            <section class="page"></section>
            ...
        </div>
        <footer class="footer"></footer>
    </div>
    <section class="panel"></section>
    

    多了一层结构,看起来有点不爽,不过使用起来还是很爽的。首先panel偏移负的本身宽度,接下来通过控制wrap-container的class来实现内容栏偏移panel的宽度

    .panel{
        position: absolute;
        top: 0;
        bottom: 0;
        left: 0;
        z-index: $zIndexOverlay;
        width: $panelWidth;
        background-color: #333;
        @include translate3d(-$panelWidth, 0, 0);
    }
    .wrap-container{
        @extend %transition-transform;  
    }
    .wrap-container.panel-active{
        @include translate3d($panelWidth, 0, 0);
    }
    

    既然这里需要一个父元素来实现一个偏移,为什么body不可以呢?所以果断干掉wrap-container,恢复最初的结构

    .panel{
        position: absolute;
        top: 0;
        bottom: 0;
        left: 0;
        z-index: $zIndexOverlay;
        width: $panelWidth;
        background-color: #333;
        @include translate3d(-$panelWidth, 0, 0);
    }
    body.has-panel{
        @extend %transition-transform;  
    }
    body.panel-active{
        @include translate3d($panelWidth, 0, 0);
    }
    

    总结

    一般来说使用比较多的还是第二种方案,因为第一种直接把左边的那个点击图标遮盖住了。而panel实际使用的时候还是挺不太好办的,因为左边的第一个icon一般都是放首页,返回什么的,当然适用不适用还是根据各自业务需要走

    系列11——弹窗modal

    本系列文章,如果没有特别说明,兼容安卓4.0.4+

    modal

    设计结构如下:

    <header class="header"></header>
    <div class="wrap-page">
        <section class="page"></section>
        ...
    </div>
    <footer class="footer"></footer>
    <div class="overlay">
        <section class="modal">
            <div class="modal-hd"></div>
            <div class="modal-bd"></div>
            <div class="modal-ft"></div>
        </section>
    </div>
    

    前面的第n篇文章提过这个overlay遮罩层的问题,现在这里说明下为什么这么设计。

    一般来说看到的overlay都与modal是兄弟元素,而不是嵌套关系。本来我也是这么设计的,这就是习惯。后来由于modal居中的问题,重新审视了下这个问题:

    为什么遮罩层的overlay与弹窗内容是兄弟元素?

    说实话真想不出什么理由,非得搞成兄弟元素。后来突然意识到以前的遮罩层如果不采用半透明图片的话,就得使用opacity(ie6-8不支持,通过滤镜模拟),而这个属性会对整个子元素都起作用,而且还没办法通过子元素覆写这个值。这是我能想到的一条最佳理由,如果还有其他理由欢迎交流。

    对于高上大的移动端来说,都是rgba时代了,所以opacity回家吃饭先。既然这个对子元素的影响已经不是问题,那么嵌套关系就可以成立,而且嵌套还有一个非常好的理由,水平垂直居中,flex小指一动即可。而兄弟元素的水平垂直居中就得设置modal的top和left的值为50%,然后再设置translate的x和y方向都-50%

    所以果断抛弃兄弟元素设计换成嵌套关系。

    modal测试

    因为overlay采用了flex布局来控制子元素居中,所以不难呢过采用display为none/block来显示隐藏遮罩层overlay,而是通过z-index的层级来控制,而modal部分通过添加删除modal-in这个class来控制显示隐藏

    scss代码如下:

    .overlay{
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        z-index: -1;
        background-color: rgba(0,0,0,.8);
        @include flex-center; // flex水平垂直居中
    }
    .overlay.active {
      z-index: 980;
    }
    
    $modalBarHeight: 40px !default;
    $modalBdPadding: 15px;
    
    .modal{
        background-color: #fff;
        border-radius: 5px;
        margin: 0 10px;
        overflow: hidden;
        opacity: 0;
        @include transform(translate3d(0,0,0) scale(0.815));
        @extend %all-transition;
        @include transition-property(transform, opacity);
    
        &.modal-in{
            opacity: 1;
            @include transform(translate3d(0,0,0) scale(1));
        }
    
        .modal-hd{
            text-align: center;
            line-height: $modalBarHeight;
            background-color: $primary;
            color: #fff;
        }
        .modal-bd{
            padding: $modalBdPadding;
        }
        .modal-ft{
            border-top: 1px solid $gray;
            @extend %display-flex;
            .btn-modal{
                @include flex(1);
                background-color: #fefefe;
                text-align: center;
                line-height: $modalBarHeight;
                color: $primary;
                &:first-child{
                    border-right: 1px solid $gray;
                }
                &:last-child{
                    border-right: none;
                }
                &:hover,&:active{
                    background-color: #d9d9d9;
                }
            }
        }
    }

    系列12——popup

    popup分为两种:一种是内容比较多,直接以全屏显示,如图一;一种是少量内容的popup提示,如图二
  • pop pagepop over

    先上demo:

    popup测试

    popup page

    第一种形式,设计结构如下:

    <header class="header"></header>
    <div class="wrap-page">
        <section class="page"></section>
        ...
    </div>
    <footer class="footer"></footer>
    <section class="popup-page">
        <div class="popup-hd"><span class="btn-close"></span></div>
        <div class="popup-bd"></div>
    </section>
    

    因为这种形式内容可能会比较长,有可能会有滚动效果,所以头部(含有关闭按钮)采用固定,动画设计使用translate偏移和opacity来搞定,这里用的是从下面进入视觉范围。通过添加删除active这个class来控制元素显示隐藏,

    scss代码如下:

    .popup-page{
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
        opacity: 0;
        background-color: #fff;
        padding-top: 35px;
        z-index: $zIndexOverlay + 5;
        @include translate3d(0, 100%, 0);
        @extend %transition-transform;
    
        &.active{
            opacity: 1;
            @include translate3d(0, 0, 0);
        }
        .popup-hd{
            position: absolute;
            left: 0;
            top: 0;
            right: 0;
            height: 35px;
            line-height: 35px;
            border-bottom: 1px solid $primary;
            padding-left: 10px;
            .btn-close{
                position: absolute;
                right: 0;
                top: 0;
                font-size: 30px;
                width: 35px;
                text-align: center;
                cursor: pointer;
                &:active,&:hover{
                    background-color: $primary;
                    color: #fff;
                }
            }
        }
        .popup-bd{
            padding: 10px;
            height: 100%;
            @extend %scroll-touch;
            p{
                margin-bottom: 10px;
            }
        }
    }
    

    popup over

    设计结构如下:

    <header class="header"></header>
    <div class="wrap-page">
        <section class="page"></section>
        ...
    </div>
    <footer class="footer"></footer>
    <div class="overlay"><section class="popup-over"></section></div>
    

    这种的首先得计算定位,这次动画采用scale和opacity来搞定,同样通过添加删除active这个class来控制元素显示隐藏

    .popup-over{
        background-color: #fff;
        border-radius: 8px;
        position: absolute;
        top: 0;
        left: 0;
        width: 200px;
        padding: 10px;
        z-index: $zIndexOverlay + 5;
        opacity: 0;
        @include transform(translate3d(0, 0, 0) scale(0.815));
        @extend %transition-transform;
    
        &.active {
          opacity: 1;
          @include transform(translate3d(0, 0, 0) scale(1));
        }
        &::before{
            @include triangle(top, 10px, #fff);
            position: absolute;
            left: 50%;
            top: -10px;
            margin-left: -10px;
        }
    }
    

    总结

    使用transition动画的时候,在动画开始之前先得确保display为非none状态,然后动画结束之后有个transtionend事件可以调用,demo中的overlay的隐藏就是这个调用了这个事件

    系列13——页面切换

    前面说的切入切出动画,一般用来前面的panel,popup,modal等效果,本篇主要说下整个页面的切换。

    布局,还是布局

    在分析页面滑动之前,我们再来讨论下之前的结构:

    <header class="header"></header>
    <div class="wrap-page">
        <section class="page"></section>
        <section class="page"></section>
        <section class="page"></section>
        ...
    </div>
    <footer class="footer"></footer>
    

    现在我们要设计page是左右滑动,首先得保证各个page在同一水平线上吧。使元素处在同一水平线的方法很多,float/position/flex/inlin-block等都可以,而这里将采用绝对定位然后配合translate,具体原因大家可以自己分析下,这里不再细说。既然page采用绝对定位,而之前的wrap-page也是采用绝对定位,所以我们有必要进一步细化下,之前我们把滚动放在wrap-page上,现在将会移动到page上,顺便把之前说的header和footer部分距离一并收拾下

    demo如下:

    css关键代码如下,主要分为布局结构class和状态结构class,两者结合使用:

    // 整体布局
    .header,.footer,.wrap-page{
      position:absolute;
      left:0;
      right:0;
      background-color: #fff;
    }
    .header,.footer{
      height:44px;
      background-color: #fff;
      text-align: center;
      z-index:900;
      line-height:44px;
    }
    .header{
      top: 0;
      border-bottom: 1px solid #f00;
    }
    .footer{
      bottom: 0;
      border-top: 1px solid #f00;
    }
    .wrap-page{
      top: 0;
      bottom: 0;
    }
    
    // page布局   
    // 默认有header无footer,所以top为header的高度,bottom为0    
    .page {
      position: absolute;
      left: 0;
      width: 100%;
      top: 44px;
      bottom: 0;
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
      overflow-scrolling: touch;
    }
    
    // layout
    // 无header有footer,所以page排除footer的高度,top为0,bottom为footer的高度
    .page-not-footer {
      top: 0;
      bottom: 44px;
    }
    // 有header和footer,所以page排除两者的高度,top为header的高度,bottom为footer的高度
    .page-not-header-footer {
      top: 44px;
      bottom: 44px;
    }
    
    // page status
    .page-active {
      -webkit-transform: translate3d(0, 0, 0);
      transform: translate3d(0, 0, 0);
    }
    .page-prev {
      -webkit-transform: translate3d(-100%, 0, 0);
      transform: translate3d(-100%, 0, 0);
    }
    .page-next {
      -webkit-transform: translate3d(100%, 0, 0);
      transform: translate3d(100%, 0, 0);
    }
    

    这样我们把对header和footer的距离交给page来控制,免去了js动态增删class,当然如果你要使用iscroll来实现滚动,那么现在就得在page下面再嵌套一层了

    动画

    下面的四种页面滑动的动画,均由忆龙Lau实践研究得出,我这借花献佛。因为最后我们使用的第四种动画,所以前三种只说下原理,感兴趣的可以私下研究,这里只提供第四种动画的demo(当然也欢迎您来补上前三个动画)

    为了简单起见,这里我们暂时不研究header和footer的变化,只研究page的滚动(header和footer部分变化我们以后再说),

    容器整体滑动

    使用容器滚动的时候,因为设置的是wrap-page容器的translate滑动,所以就不需要page status这几个class了,然后所有的page页面一字排开,按照translate距离0,100%,200%,300%等差数列排开,如下图:

    page slide 1

    单个页面滑动

    主要是将要呈现的页面的一半先覆盖在目前页面上并设置opacity为0,然后使用transition动画,改变opacity及translateX为0

    page slide 2

    双页联动滑动

    这种将会涉及两个page页面的动画,当前页面和即将呈现的页面同时向左或向右偏移100%,两者的运动频率一样,跟第一种容器整体滑动的效果看起来是没什么区别的

    page slide 3

    双页视差滑动

    在第三种的基础上提供一种视差效果,让当前页面的离开和即将呈现的页面运动频率不一致,以达到视觉上的美感

    page slide 4

    双页视差滑动 demo

    进入离去的css如下:

    .page-in {
      -webkit-transition: -webkit-transform 300ms cubic-bezier(0.42, 0, 0.58, 1);
      -ms-transition: transform 300ms cubic-bezier(0.42, 0, 0.58, 1);
      transition: transform 300ms cubic-bezier(0.42, 0, 0.58, 1);
    }
    
    .page-out {
      -webkit-transition: -webkit-transform 400ms cubic-bezier(0.42, 0, 0.58, 1) 0.1s;
      -ms-transition: transform 400ms cubic-bezier(0.42, 0, 0.58, 1) 0.1s;
      transition: transform 400ms cubic-bezier(0.42, 0, 0.58, 1) 0.1s;
    }
    

    关于这个demo有下面几点需要注意:

    •  page-in进入的动画比较快,然后无延迟;page-out离去的动画比较慢,延迟0.1s
    •  运动函数的选择。这里使用的是cubic-bezier(0.42, 0, 0.58, 1),话说这是忆龙Lau无数次尝试后,偶然从一个国外站点翻阅的得到的一个最佳运动曲线。
    •  运动结束后得移除各自的page-in和page-out类


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值