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

项目是通过cli
方式创建的vue3+vite
的uniapp
项目,为了项目能快速稳定地修改上线,下文通过编写两个vite
插件快速实现调整应用字体大小的需求。
二、实现方法说明
从微信基础库2.9.0
开始,新增了page-meta组件,它是一个特殊的标签,有点类似html
里的header
标签。在这个标签上可以设置一个root-font-size
属性,以实现动态切换字体大小的效果。
下图是page-meta组件的属性说明:

了解了如何实现,下面就可以开始制定实现方法了:
1.由于相对的是
rem
单位,所以需要把页面当中的rpx
和px
的像素单位统一转换成rem
。
2.在page-meta
组件上绑定全局响应式字体大小变量 ,并通过vite
插件自动插入到每个页面顶部,避免手动一个个页面写入,提升开发效率。
3.新增字体大小配置页面,动态调整page-meta
标签的root-font-size
属性字体大小。
三、开发两个vite插件开发
1.px和rpx转换为rem
转换方法需要依赖postcss-pxtorem
插件,先运行安装开发依赖
shell
pnpm add postcss-pxtorem -D
pxtorem-plugin.js
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
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.调整全局字体大小页面

ts
<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
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>`)
]
});
项目当中rpx
和px
混用时,需要依次把这两个像素单位都转换成rem
,以保持调整字体大小后页面的一致性; 接着向每个页面顶部插入\n <page-meta :root-font-size="$getRootFontSize()"></page-meta>
,其中$getRootFontSize()
为全局方法,获取动态跟字体大小,如下:
2.挂载获取字体大小全局方法
ts
// main.ts
import { useFont } from "./hooks/useFont";
const { rootFontSize } = useFont();
export function createApp() {
app.config.globalProperties.$getRootFontSize = () => rootFontSize.value +'px'
return {
app
};
}
当然,你也可以设置一个
store
当中的全局属性,这个大家灵活运用即可。
3.useFont.ts
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
插件,除了动态调整字体大小之外,还可以为你平时开发哪些方面赋能呢?
期待大家可以举一反三 ,多多思考或评论区交流~