kbone request、canvas及屏幕适配同构方案

2020-04-22

一 canvas

一个好消息是小程序基础库 v2.9.0 正式开放一套全新的 Canvas 接口。该接口符合 HTML Canvas 2D 的标准,实现上采用 GPU 硬件加速,渲染性能相比于现有的 Canvas 接口有一倍左右的提升。

新版 Canvas 2D 接口与 Web 几乎完全一致了。这对 H5 和小程序的同构有很大的便利。

要注意的就是新版的 Canvas 可以不需要 canvas-id,但是必须要 type=”2d”;

目前是两种 context 是共存的。

kbone 里通过prepare 的需要有 type=”2d”

canvas 在小程序里空白

初次使用 canvas,常会遇到在小程序里空白的问题,踩过的坑如下:

1 canvas 没显式定义宽高

没有显式定义宽高的情况下,web 还能显式正常,默认宽高是 300**150:

miniprogram 里没有显式定义宽高的情况下,虽然默认宽高也是 300**150,但是画布空白(这一点 kbone 可以做到才对,计划有时间就去看下这个问题):

以下 2 种方式设置宽高都可以,看业务场景选择,但是注意不能用 style 设置宽高,内联的也不行:

1、

2、

显式设置后再看下 web 和 mp 的表现:

2 canvas 获取 dom 未成功

还有一种情况是在小程序里获取 dom 没有成功。 可以将在获取 dom 后将 dom 打印出来看看。 遇到的一个没有结论的问题是:

通过 ref 获取 canvas dom 还是通过 querySelector/getElementById

通过 ref 获取,可以看到 web 是没有问题的,和用 id 获取是一致的。但是 mp 里,曾经遇到过 canvas.js 里 res 节点为 null 的问题,即$$prepare 回调函数的参数 domNode 为 null。但是后面再跑又莫名消失了。尚未定位原因。

二 request

request:本来是计划用 kbone-api 里面提供的 request api,但是它的 request 里对 url 要求是 http https 完整路径的请求格式: kbone-api-request-validUrl

这种情况下无法用 devserver 的 proxy 功能(除非自己起一个接口服务)。为了开发方便,更换为了 axios 和 axios-miniprogram-adapter,下面是我们二次封装的 request 模块:

// src/request/http.js
/* eslint-disable */
import axios from 'axios';
import mpAdapter from 'axios-miniprogram-adapter';
import QS from 'qs';

if (process.env.isMiniprogram) {
  axios.defaults.adapter = mpAdapter;
}

// 请求拦截
axios.interceptors.request.use(
  config => {
    // config.headers = {
    //   'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
    // }
    return config;
  },
  error => Promise.error(error)
);

// 响应拦截
axios.interceptors.response.use(
  response => {
    let { data } = response;
    if (response.status === 200) {
      if (data && data.code != '0') {
        // data.code非0则接口异常
        return Promise.reject(data);
      }
      return Promise.resolve(data.resultData);
    } else {
      return Promise.reject(data);
    }
  },
  // 服务器状态码不是2开头的的情况
  // 这里可以跟后台开发人员协商好统一的错误状态码
  // 然后根据返回的状态码进行一些操作,例如登录过期提示,错误提示等等
  // 下面列举几个常见的操作,其他需求可自行扩展
  error => {
    const { response } = error;
    if (response) {
      switch (response.status) {
        // 401: 未登录
        case 401:
          // 未登录则跳转登录页面,并携带当前页面的路径
          // 在登录成功后返回当前页面,这一步需要在登录页操作。
          break;
        // 403 token过期
        case 403:
          // 登录过期对用户进行提示toast或者dialog
          // 清除本地token和清空vuex中token对象
          // 跳转登录页面
          break;

        // 404请求不存在
        case 404:
          // 对用户进行提示toast或者dialog
          break;
        // 其他错误,直接抛出错误提示
        default:
        // 其他对应处理或者直接对用户进行提示toast或者dialog
      }
      return Promise.reject(response.data);
    } else {
      // 这里处理断网的情况 进行提示
    }
  }
);

/**
 * get方法,对应get请求
 * @param {String} url [请求的url地址]
 * @param {Object} params [请求时携带的参数]
 */
export function get(url, params) {
  return new Promise((resolve, reject) => {
    axios
      .get(url, {
        params: params
      })
      .then(res => {
        resolve(res);
      })
      .catch(err => {
        reject(err);
      });
  });
}

/**
 * post方法,对应post请求
 * @param {String} url [请求的url地址]
 * @param {Object} params [请求时携带的参数]
 */
export function post(url, params) {
  return new Promise((resolve, reject) => {
    axios
      .post(url, QS.stringify(params))
      .then(res => {
        resolve(res);
      })
      .catch(err => {
        reject(err);
      });
  });
}
// src/request/api.js
const url = {
  test: '/api/forward?apiType=micro_service'
};
console.log(process.env.NODE_ENV);
if (process.env.NODE_ENV === 'production' || process.env.isMiniprogram) {
  Object.keys(url).forEach(item => {
    url[item] = `https://xxxx.com${url[item]}`;
  });
}

export default url;

在 main.js 里绑定为 vue 的全局 api

在业务中调用:

import url from '../../request/api';
this.$get(url.test, {})
  .then(res => {
    this.$api.showToast({
      title: '请求成功'
    });
    console.log('--------requestTest----------');
    console.log('url.test:' + url.test);
    console.log(res);
  })
  .catch(() => console.log('promise catch err'));

三 屏幕适配

kbone 的官网上介绍它没有用小程序的 rpx 进行屏幕适配,取而代之的是使用更为传统的 rem 进行开发。

理论上支持 vw 没问题。这里采用 postcss-px-to-viewport 自动将 px 转换为 vw,进行屏幕适配。

首先

npm install postcss-px-to-viewport --save-dev

或者使用 yarn 安装

yarn add postcss-px-to-viewport -D

在工程目录下建 postcss.config.js

postcss-loader 默认是会从项目根目录获取这个配置项。但是有优先级,webpack 里配置的 options 优先级是高于独立配置文件的。

而 kbone 的几个构建方式有差异,option 在每个 webpack 里不完全相同,无法公用同一个 postcss.config.js.所以我们在 postcss.config.js 里只存放公用的插件配置,引入 webpack 的几个配置文件中。

需要注意的是,postcss-loader 的 options 里配置 plugins 时只接受 array 或者以及 function,不接受 object(与自动加载项目根路径的 postcss.config.js 接受 object 的常见写法不同)。

kbone 的配置里已经是数组形式,所以我们也继续写为数组形式,以匹配原来的配置。

具体代码如下:

// ./postcss.config.js
const pxtovw = require('postcss-px-to-viewport');
module.exports = {
  plugins: [
    new pxtovw({
      unitToConvert: 'px', //需要转换的单位,默认为"px";
      viewportWidth: 375, //设计稿的视口宽度
      unitPrecision: 3, //单位转换后保留的小数位数
      propList: ['*'], //要进行转换的属性列表,*表示匹配所有,!表示不转换
      viewportUnit: 'vw', //转换后的视口单位
      fontViewportUnit: 'vw', //转换后字体使用的视口单位
      selectorBlackList: ['.ignore', '.hairlines'], //不进行转换的css选择器,继续使用原有单位
      minPixelValue: 1, //设置最小的转换数值
      mediaQuery: false, //设置媒体查询里的单位是否需要转换单位
      replace: true, //是否直接更换属性值,而不添加备用属性
      exclude: [/node_modules/] //忽略某些文件夹下的文件
    })
  ]
};

以 webpack.dev.config.js 为例:

// ./build/webpack.dev.config.js
const postcssConfig = require('../postcss.config');
...

module: {
    rules: [{
      test: /\.(less|css)$/,
      use: [{
        loader: 'vue-style-loader',
      }, {
        loader: 'css-loader',
      }, {
        loader: 'postcss-loader',
        options: {
          plugins: [
            autoprefixer, postcssConfig.plugins[0]
          ],
        }
      }, {
        loader: 'less-loader',
      }],
    }],
  },

...

reference:

kbone 使用 rem

vue-cli 中使用 postcss-px-to-viewport

webpack4 配置之 postcss-loader

postcss 配置文件优先级问题

个人 blog