一、背景

最近在上线的微信小程序中遇到了一个需求,由于目标用户普遍年龄比较大,可能视力存在一些老花现象,所以要求对应用的字体调大一些,下图是产品反馈的需求:

817b7836f93db4fb473f1979ae9f7466.jpg

项目是通过cli方式创建的vue3+viteuniapp项目,为了项目能快速稳定地修改上线,下文通过编写两个vite插件快速实现调整应用字体大小的需求。

二、实现方法说明

从微信基础库2.9.0开始,新增了page-meta组件,它是一个特殊的标签,有点类似html里的header标签。在这个标签上可以设置一个root-font-size属性,以实现动态切换字体大小的效果。

下图是page-meta组件的属性说明:

image.png

了解了如何实现,下面就可以开始制定实现方法了:

三、开发两个vite插件开发

1.px和rpx转换为rem

转换方法需要依赖postcss-pxtorem插件,先运行安装开发依赖

pnpm add postcss-pxtorem -D

pxtorem-plugin.js

import pxtorem from 'postcss-pxtorem';

export default function pxtoremPlugin(options = {}) {
  // 自定义默认配置
  const defaultOptions = {
    rootValue: 16, // 根元素字体大小,1rem = 16px
    unitPrecision: 5, // 转换精度
    propList: ["font", "font-size", "line-height", "letter-spacing"], // 需要转换的属性列表,*表示所有属性
    selectorBlackList: [], // 忽略的选择器
    replace: true,  // 是否替换
    mediaQuery: false,  // 是否转换媒体查询
    minPixelValue: 0, // 最小转换像素值
    exclude: null,  // 排除的文件
    unit: "px", // 转换单位
  };

  // 合并参数配置
  const mergedOptions = { ...defaultOptions, ...options };

  return {
    name: 'vite-pxtorem-plugin',
    enforce: 'post',

    // 配置PostCSS
    config: () => {
      return {
        css: {
          postcss: {
            plugins: [
              pxtorem(mergedOptions)
            ]
          }
        }
      };
    }
  };
}

2.自动向页面顶部插入page-meta标签

page-root-insert-element.js

import { parse } from '@vue/compiler-dom'

export default function pageRootinsertElementPlugin(content = '') {
  return {
    name: 'vite-pageRootInsertElement-plugin',
    enforce: 'pre',

    transform(code, id) {
      // 排除node_modules
      if (id.includes('node_modules')) return

      // 排除不是pages目录下的文件
      if (!//pages//.test(id)) return

      // 排除components目录下的文件
      if (id.includes('components/')) return

      // 排除不是.vue结尾的文件
      if (!id.endsWith('.vue')) return

      console.log('id', id)

      try {
        // 解析文.vue文件内容为虚拟节点: code为.vue文件内容
        const parsed = parse(code, {
          comments: true,
          onError: (err) => {
            console.log('解析错误id', id)
            console.warn('code解析错误:', err.message)
          }
        })
        // 查找template虚拟节点,即虚拟dom
        const templateNode = parsed.children.find(node => node.tag === 'template')
        if (!templateNode) return

        // 获取template内容,即<template>...省略中间内容</template>模板字符串
        const templateContent = code.slice(templateNode.loc.start.offset, templateNode.loc.end.offset)

        /** 对template标签下的内容进行头尾拆分,方便在头部插入page-meta标签 */

        // 1.获取第一个template标签的>位置索引
        const insertPosition = templateContent.indexOf('>') + 1

        // 2.获取template标签的头部:<template>
        const beforeInsert = templateContent.slice(0, insertPosition)

        // 3.获取template标签的尾部
        const afterInsert = templateContent.slice(insertPosition)

        // 4.头部插入page-meta,拼接成新的template内容
        const newTemplateContent = beforeInsert + content + afterInsert

        // 替换原template内容
        const newCode = code.replace(templateContent, newTemplateContent)

        return newCode
      } catch (e) {
        console.warn('page-root-insert-element插件处理失败:', e.message)
        return code
      }
    }
  }
}

以上只是一个示例,可以根据自身项目结构修改需要向哪些文件插入内容或者排除哪些文件。

3.调整全局字体大小页面

font.gif

<template>
  <view class="page">
    <view class="slider-box">
      <view class="label-box">
        <text class="label" v-for="item in fontSizeList" :key="item.label">{{ item.label }}</text>
      </view>
      <uv-slider :value="value" :step="step" :min="0" :max="100"
        @change="onChange"
      ></uv-slider>
    </view>
  </view>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { useFont } from '@/hooks/useFont'

const { rootFontSize, setRootFontSize } = useFont()

const fontSizeList = [
  { label:  '超小号', fontSize: 10 },
  { label:  '小号', fontSize: 12 },
  { label:  '标准', fontSize: 16 },
  { label:  '大号', fontSize: 20 },
  { label:  '超大号', fontSize: 26 },
]

const step = 100 / (fontSizeList.length - 1)

const defaultIndex = fontSizeList.findIndex(item => item.fontSize === rootFontSize.value)

// 计算当前字体大小所在滑块值
const value = ref(defaultIndex >= 0 ? step * defaultIndex : step * 2 )

const onChange = (val: number) => {
  console.log('value', val)
  // 计算当前是fontSizeList的哪个索引
  const index = val / step
  setRootFontSize(fontSizeList[index].fontSize)
}
</script>

<style scoped lang="scss">
.logo {
  width: 200rpx;
  height: 200rpx;
}
.slider-box {
  padding: 20rpx;
  .label-box {
    display: flex;
    justify-content: space-between;
    .label {
      text-align: center;
    }
  }
}
</style>

四、vite插件用法

1.vite.config.ts

import { defineConfig } from "vite";
import uni from "@dcloudio/vite-plugin-uni";
import { loadEnv } from "vite";
import pxtoremPlugin from "./plugins/pxtorem-plugin";
import pageRootIntertElement from "./plugins/page-root-insert-element";

const env = loadEnv("development", process.cwd())

export default defineConfig({
  plugins: [
    uni(),
    // 转换rpx为rem
    // @ts-ignore
    pxtoremPlugin({
      rootValue: 32, // 16px=32rpx默认根节点字体大小
      unit: 'rpx',
      // 转换字体大小rpx为rem
      // propList: ['font-size']
    }),
    // 转换px为rem
    // @ts-ignore
    pxtoremPlugin({
      rootValue: 16,
      unit: 'px',
      // 转换字体大小rpx为rem
      // propList: ['font-size']
    }),
    // @ts-ignore
    pageRootIntertElement(`n  <page-meta :root-font-size="$getRootFontSize()"></page-meta>`)
  ]
});

项目当中rpxpx混用时,需要依次把这两个像素单位都转换成rem,以保持调整字体大小后页面的一致性; 接着向每个页面顶部插入n <page-meta :root-font-size="$getRootFontSize()"></page-meta>,其中$getRootFontSize()为全局方法,获取动态跟字体大小,如下:

2.挂载获取字体大小全局方法

// main.ts
import { useFont } from "./hooks/useFont";
const { rootFontSize } = useFont();

export function createApp() {
  app.config.globalProperties.$getRootFontSize = () => rootFontSize.value +'px'
  return {
    app
  };
}

3.useFont.ts

import { ref } from "vue";
const rootFontSize = ref(16);

export const useFont = () => {
  // 初始化根节点字体大小
  const size = uni.getStorageSync("rootFontSize")
  if(size) {
    rootFontSize.value = size
  }
  return {
    rootFontSize,
    setRootFontSize: (value: number) => {
      rootFontSize.value = value;
      // 本地持久化
      uni.setStorageSync("rootFontSize", value);
    }
  };
}

五、结语

如果需求是不需要动态调整字体大小页面,可以直接在page-meta组件指定一个具体的字体大小即可。

如果是其他vite项目,思想依然是通用的,重要的是理解vite插件用法。

通过开发vite插件,除了动态调整字体大小之外,还可以为你平时开发哪些方面赋能呢?

期待大家可以举一反三,多多思考或评论区交流~

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]