✨项目上线后产品要求把应用字体改大点📏怎么办?一招教你快速解决🔧

一、背景

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

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

二、实现方法说明

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

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

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

1.由于相对的是rem单位,所以需要把页面当中的rpxpx的像素单位统一转换成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>`)
  ]
});

项目当中rpxpx混用时,需要依次把这两个像素单位都转换成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插件,除了动态调整字体大小之外,还可以为你平时开发哪些方面赋能呢?

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

相关推荐
xvmingjiang2 小时前
Vue 3 中监听多个数据变化的几种方法
前端·javascript·vue.js
我有一只臭臭2 小时前
ES5 和 ES6 类的实现
前端·javascript·es6
excel2 小时前
Three.js 实现高分辨率地球边界可视化
前端
LaoZhangAI2 小时前
Google Gemini AI图片编辑完全指南:50+中英对照提示词与批量处理教程(2025年9月)
前端·后端
用户11481867894842 小时前
从零搭建 Vue3 + Nest.js 实时通信项目:4 种方案(短轮询 / 长轮询 / SSE/WebSocket)
前端·后端
LaoZhangAI2 小时前
Google Gemini Nano与Banana AI完整部署指南:2025年轻量级AI解决方案
前端·后端
李重楼2 小时前
Vite 默认端口启动被拒绝解决办法
vite
用户11481867894842 小时前
基于 Webpack Module Federation 的 Vue 微前端实践
前端
怪可爱的地球人2 小时前
Pinia状态管理有哪些常用API?
前端