H5通用功能点汇总

elegantYU 发布于

作为一名有操守的程序员,鄙人,只发干货!

此篇文章作为 h5 前端路上的功能点、工具总结;如有更好的方法,请在评论区留下你的身影

连做三个 h5 项目,或多或少积累一点开发的经验,此篇文章一是记录,二为分享。

开整!

基础环境

三次项目皆使用 vue 框架开发,使用 vue-cli3.x 生成项目主体已是相当熟稔,vue-cli 传送门

使用 axios 请求库,也可自行选择顺手的使用。

微信 sdk 的再次封装

微信官方提供一个 jsbridge 脚本,能够很方便的使用设备的一些功能及微信本身提供的功能。

我们需要做的是在项目中更好的调用它。

  • public/index.html模版 html 里的 head 中加入 wxsdk(传送门),或直接复制此条http://res.wx.qq.com/open/js/jweixin-1.4.0.js(微信目前最新版本 js)

  • src目录下,我们创建一个utils文件夹内创建wxsdk.js,在其中加上

import axios from "axios";

const wxsdk = {
  init(url) {
    // 顾名思义,用于初始化wxconfig的方法
    return new Promise(resolve => {
      axios
        .get("请求微信config的后端接口", { params: { url } })
        .then(({ appId, timestamp, nonceStr, signature }) => {
          wx.config({
            debug: false, // 后面可以用于调试config是否配置成功
            appId,
            timestamp,
            nonceStr,
            signature,
            jsApiList: [
              "chooseImage",
              "uploadImage",
              "getLocalImgData",
              "updateAppMessageShareData",
              "updateTimelineShareData"
            ] // 在此注册你需要使用的api
          });
          resolve(); //  配置完成的promise回调
        });
    });
  }
};

export default wxsdk;
  • 下面把初始化方法放入全局的路由守卫中,使用户在每个页面打开时都注册一次。
// router.js中
import Vue from 'vue'
import Router from 'vue-router'
import wxsdk from '../utils/wxsdk'

Vue.use(Router)

const router = new Router({
    //  没看错,使用的是hash,
    //  使用hash是因为方便后端直接使用链接进行其他的带参操作
    //  同时,也避免了在nginx中部署时对前端路由的操控
    mode: 'hash',
    routes: [...]
})

router.afterEach((to, from, next) => {
    // 此处较为关键,由于我们使用的是单页应用,内部路由皆为虚拟路由,
    // 并且是hash模式,需要获取 # 前的路由作为请求路径
    wxsdk.init(location.href.split('#')[0])
})

export default router
  • 加入一个封装上传本地图片方法作为例子
chooseImage (count) {
    return new Promise(resolve => {
      wx.ready(() => {
        wx.chooseImage({
          count, //  用于控制上传图片的数量
          sizeType: ['original', 'compressed'],
          sourceType: ['album', 'camera'],
          success: ({ localIds }) => {
            resolve(localIds)
          }
        })
      })
    })
},
  • 若项目中有需要判断微信的 js 是否加载完毕,可以使用下面方法
// wxsdk.js中
const wxsdk = {
    ...,
    check (cb) {
      if (typeof WeixinJSBridge === 'object' && typeof WeixinJSBridge.invoke === 'function') {
        cb()
      } else {
        if (document.addEventListener) {
          document.addEventListener('WeixinJSBridgeReady', cb, false)
        } else if (document.attachEvent) {
          document.attachEvent('WeixinJSBridgeReady', cb)
          document.attachEvent('onWeixinJSBridgeReady', cb)
        }
      }
    },
}
  • 可以在页面的mounted周期中引入
<script>
export default {
  mounted() {
    this.$wxsdk.check(() => {
      console.log("微信js加载完毕,进入页面");
    });
  }
};
</script>
  • 为了方便我们对 wxsdk 的使用,可以在main.js中将此对象挂载到 vue.prototype 上
import wxsdk from "./utils/wxsdk";

Vue.prototype.$wxsdk = wxsdk;

微信 jssdk 的封装使用基本完成。

页面适配

我使用的是 rem 单位布局,根据设配的屏幕宽度与设计稿的宽度比计算出根元素的像素大小,从而使一张设计图适配不同的手机屏幕

// utils/util.js中

const deviceRem = size => {
  const docEl = document.documentElement;
  const clientWidth = docEl.clientWidth;
  const reSize = () =>
    (docEl.style.fontSize =
      clientWidth >= size ? "100px" : 100 * (clientWidth / size) + "px");
  document.addEventListener("DOMContentLoaded", reSize, false);
};
deviceRem(1125);

第三方字体包

根据 h5 的不同业务特点,可能需要引入各种个样的字体包,比如我的第一个 h5 就使用了站酷快乐体xxx的字体包

@font-face {
  font-family: "happyFonts";
  src: url("../fonts/站酷快乐体2016修订版.ttf");
  font-weight: normal;
  font-style: normal;
}

body {
  font-family: "happyFonts";
}

引入后,打包到线上环境测试发现,即使字体包用font-spider筛选一遍,可真机上打开或多或少会出现字体加载过慢,导致文字区域空白一段时间再出现,或者先出现系统默认字体,之后再突然出现字体包字体。

这种体验…很难受啊!

所以我们急需一个能够监听第三方字体包是否加载完成的方法。

抓耳挠腮,捶胸顿足之下,终于找到一个神器 Font Face Observer!!!

如同描述的一般,它提供两个回调函数,字体加载成功或失败,页面可以在其回调内决定是否出现。

import Font from "fontfaceobserver";

export default {
  mounted() {
    const font = new Font("happyFonts");
    font.load().then(
      () => {
        console.log("字体加载完成");
        this.$router.replace({ name: "start" });
      },
      () => {
        console.log("字体加载失败");
        this.$router.replace({ name: "start" });
      }
    );
  }
};

BGM!

大部分活动 h5 少不了会用上 bgm,建议 bgm 使用某一段或几段可以循环播放的音频,越短越好(占用加载时间和服务器资源)。

“音频文件太长了,我只需要其中一小段怎么办”,我给出的方法是:找一个会剪音乐的漂亮妹子,嘿嘿嘿(咽口水)

或者点击此处

好了,由于 h5 中的 bgm 占用了和字体包差不多的大小,我们也需要一个能够判断音频文件加载完成的事件。

下面是 audio 相关的事件

<body>
  <audio id="music" src="" loop="loop"></audio>
  <script>
    /**
     * @func play 播放 可判断音频是否正在播放
     * @func pause 暂停 可判断音频是否暂停
     * @funcs  一下事件是音频在加载过程中依次触发的
     * loadstart => durationchange => loadeddata => progress => canplay => canplaythrough
     */
    const audio = document.getElementById("music");
    audio.oncanplay = () => {
      console.log("音乐可以播放,做你想做的吧");
    };
  </script>
</body>

正在我志得意满打开测试链接时,发现无论如何点击播放都没有音乐声音!😱

着急忙慌打开微信开发者工具调试后发现,我的音乐地址找不到资源,这是为何?分明就放在src/assets/music中啊,找不到资源…难不成!

心头一点灵光乍现,当即打开vue-cli,急急望去

{
  video: ['src', 'poster'],
  source: 'src',
  img: 'src',
  image: ['xlink:href', 'href'],
  use: ['xlink:href', 'href']
}

果然不出所料,vue-cli 的 loader 配置默认没有 audio 的配置!至此,情绪再次恢复平静,打开vue.config.js,缓缓书写几行代码

module.exports = {
  assetsDir: "static",
  chainWebpack: config => {
    config.module
      .rule("vue")
      .use("vue-loader")
      .loader("vue-loader")
      .tap(options => {
        options.transformAssetUrls = {
          audio: "src"
        };
        return options;
      });
  }
};

重新打包,点开dist文件夹,static/media/xxx.mp3已安稳的进入其中,吾心甚慰。

html2canvas

h5 的最后推广,同样少不了的一个功能就是,生成图片。

对于生成图片,这里有两个模块可供使用html2canvasdom-to-image,两个模块的使用方法都很简单,这里使用的是 html2canvas.

随之而来的就是老生常谈的两个问题,一是图片跨域问题,二是生成图片清晰度优化。

  • 图片跨域

    启用 html2canvas 中的{ useCORS: true }配置,即可渲染 cdn 的图片和本地图片。

    若有其他来源图片(比如微信用户头像),要么走接口转化下图片链接,要么将图片转成 base64 格式再行渲染即可。

  • 清晰度优化

    1. 将选区 dom 的布局单位改为 px 像素
    2. 使用{ scale: 2 }将 canvas 的宽高扩大为原本 dom 的两倍,或设置为 devicePixelRatio 倍,最后将生成图片的大小设置为原先 dom 大小
    3. 可以使用 css 绘制的样式,尽量不要使用图片代替
    <script>
    import html2canvas from "html2canvas";
    
    export default {
      // 获取可视区域的宽高
      getClientSize() {
        const app = this.$refs.app;
        return [app.clientWidth, app.clientHeight];
      },
      // 生成图片
      createPhoto() {
        const content = document.getElementById("container");
        const canvas = document.createElement("canvas");
        const [width, height] = this.getClientSize();
        const scale = 2;
        canvas.width = width;
        canvas.height = height;
        canvas.getContext("2d").scale(scale, scale);
    
        const opts = {
          logging: true, //  打印生成图片的日志
          canvas: canvas, //  导入生成的canvas
          width: width,
          height: height,
          scale: scale,
          useCORS: true, //  允许图片跨域
          timeout: 1000 //  设置图片超时时间
        };
        html2canvas(content, opts).then(c => {
          this.photo = c.toDataURL("image/png", 1);
        });
      }
    };
    </script>

    完美~

图片压缩

图片压缩有我们熟知的熊猫压缩,这个在线压缩网站可以极大的减少图片的大小同时不会出现失真,但对于工程师来说,拖入本地图片压缩的做法未免略显蠢笨。

于是,有了这么一个图片压缩的工具imageMin,及其 webpack 的扩展imagemin-webpack-plugin

const ImageminPlugin = require('imagemin-webpack-plugin').default
const IS_PROD  = ['production', 'prod'].includes(process.env.NODE_ENV)  //  判断当前环境

module.exports = {
  configureWebpack: {
    plugins: {
      new ImageminPlugin({
        disable: !IS_PROD,
        pngquant: {
          quality: '80-100',  //  控制图片质量区间
          progressive: true   //  图片加载时的显示方式是否是渐变出现
        }
      })
    }
  }
}

亲测实用,image文件夹原本 5mb(已被 tinypng 压缩),打包后只有 3.5mb。