25 Vue3之如何开发移动端并适配

开发移动端最主要的就是适配各种手机

vw vh是相对viewport 视口的单位,配合meta标签可以直接使用,无需计算

1vw=1/100视口宽度

1vh=1/100视口高度

当前屏幕视口是375像素,1vw就是3.75px

postCss 提供了 把Css 转换AST的能力,类似于Babel,为此我们可以编写一个postCss插件用于将px转换为vw

前置知识

px 固定的单位不会随着屏幕大小的变化而变化

百分比

  • 百分比是子元素占父元素的宽度,然后让子元素撑起父元素的高度
  • 其中百分比只能勉强解决容器的适配(比如高度无法用百分比表示),做不到字体的适配
  • 字体和高度适配也需配合下面的rem方案

flex

     跟百分比一样只能解决容器的适配不能处理字体的适配

rem

  • 在之前我们用的是rem 根据根节点HTML font-size 去做缩放 
  • rem r=root 1rem = html 假如html根节点font-size =16px 1rem = 16px
  • 有个问题375屏幕下适合多少HTML font-size 并不清楚之前引入的是淘宝的flexible.js来计算的,这个方案就额外多了计算的开销
vh、vw
  •  vw、vh是基于视口的布局方案,所以这个meta元素的视口必须声明。(解决宽高自动适配)
  • 为什么加meta标签 默认的视口可能是大于屏幕的尺寸会出现滚动条

  • vw vh是相对viewport 视口的单位,配合meta标签可以直接使用,无需计算

    1vw=1/100视口宽度

    1vh=1/100视口高度

    当前屏幕视口是375像素,1vw就是3.75像素

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no“>

移动设备具有各种不同的屏幕尺寸和分辨率,例如智能手机和平板电脑。为了提供更好的用户体验,网页需要根据设备的屏幕宽度进行自适应布局。如果不设置width=device-width,移动设备会按照默认的视口宽度(通常是较宽的桌面屏幕)来渲染网页,导致网页内容在移动设备上显示不正常,可能出现内容被截断或需要水平滚动的情况

初始化项目

npm init vite@latest

cnpm i 

cnpm i less less-loader

cnpm i @vueuse/core

postCss

https://cn.vitejs.dev/config/shared-options.html#css-postcss

发现vite已经内置了postCss

https://www.postcss.com.cn/

postCss 提供了 把Css 转换AST的,类似于Babel,为此我们可以编写一个插件用于将px转换为vw

根目录新建一个plugins文件夹新建两个文件pxto-viewport.ts type.ts 

然后在 tsconfig.node.json 的includes 配置 "plugins/**/*",

compilerOptions 配置 noImplicitAny:false

tsconfig.node.json配置

postcss-px-to-viewport.ts

// postcss的插件已经是vite内置了,所以不需要再单独安装了

import type { Options } from "./type.ts";
import type { Plugin } from "postcss";
const defaultOptions = {
  viewPortWidth: 375, //视窗的宽度,对应的是我们设计稿的宽度一般是375,ui设计稿宽度是多少就是多少
  mediaQuery: false,
  unitToConvert: "px",
};
export const pxToViewport = (
  options: Options = defaultOptions
): Plugin => {
  const opt = Object.assign({}, defaultOptions, options);
  return {
    postcssPlugin: "postcss-px-to-viewport",
    //  AtRulede等钩子函数
    //
    //css节点都会经过这个钩子
    Declaration(node) {
      // console.log(node);
      console.log(node.prop, node.value);
      // console.log("opt.viewPortWidth", opt.viewPortWidth); // 375

      const value = node.value;
      //匹配到px 转换成vw
      if (value.includes(opt.unitToConvert)) {
        const num = parseFloat(value); //考虑到有小数的情况
        const transformValue =
          (num / opt.viewPortWidth) * 100;
        node.value = `${transformValue.toFixed(2)}vw`; //转换之后的值
      }
    },
  };
};

 type.ts

export interface Options {
    viewPortWidth?: number;
    mediaQuery?: boolean;
    unitToConvert?: string;
}

vite.config.ts配置

引入我们写好的插件

https://cn.vitejs.dev/config/shared-options.html#css-postcss参考配置

import { pxToViewport } from "./plugins/postcss-px-to-viewport";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  css: {
    postcss: {
      plugins: [pxToViewport()],
    },
  },
});

用户可自定义设计稿宽度 (可省略在插件内部已给了默认值)

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { pxToViewport } from "./plugins/postcss-px-to-viewport";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  css: {
    postcss: {
      plugins: [
        pxToViewport({
          unitToConvert: "px", // 要转化的单位
          viewPortWidth: 750, // UI设计稿的宽度
        }),
      ],
    },
  },
});

组件中使用

自适应效果图

未使用插件前明显感觉中间被挤压了

使用插件后

全局换肤案例

全局字体更改案例

完整示例代码 

main.ts中注释掉初始style.css样式

// postcss的插件已经是vite内置了,所以不需要再单独安装了

import type { Options } from "./type.ts";
import type { Plugin } from "postcss";
const defaultOptions = {
  viewPortWidth: 375, //视窗的宽度,对应的是我们设计稿的宽度一般是375,ui设计稿宽度是多少就是多少
  mediaQuery: false,
  unitToConvert: "px",
};
export const pxToViewport = (
  options: Options = defaultOptions
): Plugin => {
  const opt = Object.assign({}, defaultOptions, options);
  return {
    postcssPlugin: "postcss-px-to-viewport",
    //  AtRulede等钩子函数
    //
    //css节点都会经过这个钩子
    Declaration(node) {
      // console.log(node);
      console.log(node.prop, node.value);
      // console.log("opt.viewPortWidth", opt.viewPortWidth); // 375

      const value = node.value;
      //匹配到px 转换成vw
      /* 
      // 无法处理 border: 1px solid red;
      if (value.includes(opt.unitToConvert)) {
        const num = parseFloat(value); //考虑到有小数的情况
        const transformValue =
          (num / opt.viewPortWidth) * 100;
        node.value = `${transformValue.toFixed(2)}vw`; //转换之后的值
      } */
      // 能处理 border: 1px solid red;
      if (value.includes(opt.unitToConvert)) {
        const regexp = new RegExp(
          `\\d+${opt.unitToConvert}{1}`,
          "gi"
        );
        const nodeVal = value.replace(regexp, (match) => {
          const num = parseFloat(match);
          const transformValue = Number(
            (num / opt.viewPortWidth) * 100
          );
          return transformValue.toFixed(2) + "vw";
        });
        node.value = nodeVal;
      }
    },
  };
};

官方插件postcss-px-to-viewport

已经帮我们处理好了各种兼容性


npm install postcss-px-to-viewport -D

vite.config.ts配置

可能vant第三库会存在挤压得问题可设置成375

const designWidth = webpack.resourcePath.includes(path.join('node_modules', 'vant')) ? 375 : 750;

import { fileURLToPath, URL } from 'url'
 
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import postcsspxtoviewport from "postcss-px-to-viewport" //插件
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(), vueJsx()],
  css: {
    postcss: {
      plugins: [
        postcsspxtoviewport({
          unitToConvert: 'px', // 要转化的单位
          viewportWidth: 750, // UI设计稿的宽度
          unitPrecision: 6, // 转换后的精度,即小数点位数
          propList: ['*'], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
          viewportUnit: 'vw', // 指定需要转换成的视窗单位,默认vw
          fontViewportUnit: 'vw', // 指定字体需要转换成的视窗单位,默认vw
          selectorBlackList: ['ignore-'], // 指定不转换为视窗单位的类名,
          minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
          mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
          replace: true, // 是否转换后直接更换属性值
          landscape: false // 是否处理横屏情况
        })
      ]
    }
  },
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

 postcss-px-to-viewport.d.ts声明文件

declare module 'postcss-px-to-viewport' {
 
    type Options = {
        unitToConvert: 'px' | 'rem' | 'cm' | 'em',
        viewportWidth: number,
        viewportHeight: number, // not now used; TODO: need for different units and math for different properties
        unitPrecision: number,
        viewportUnit: string,
        fontViewportUnit: string,  // vmin is more suitable.
        selectorBlackList: string[],
        propList: string[],
        minPixelValue: number,
        mediaQuery: boolean,
        replace: boolean,
        landscape: boolean,
        landscapeUnit: string,
        landscapeWidth: number
    }
 
    export default function(options: Partial<Options>):any
}

引入声明文件 tsconfig.app.json postcss-px-to-viewport.d.ts跟vite.ts同级

{
  "extends": "@vue/tsconfig/tsconfig.web.json",
  "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "postcss-px-to-viewport.d.ts"],
  "exclude": ["src/**/__tests__/*"],
  "compilerOptions": {
    "composite": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

 

<think>嗯,用户问的是如何在Vue3项目中实现移动端到大屏的适配方案。首先,我需要回忆一下移动端适配的常用方法。根据之前的引用内容,用户可能已经了解了一些rem适配方案,比如rem配合媒体查询、js动态设置或者使用VW。但这次的问题是要适配移动端到大屏,可能需要更灵活的响应式设计。 首先,我应该考虑Vue3的特性,比如组合式API和响应式工具,可能可以用来动态调整布局。然后,适配方案可能需要结合多种方法,比如使用rem或vw作为单位,同时利用媒体查询来针对不同屏幕尺寸应用不同的样式。另外,大屏可能需要更多的布局调整,比如弹性布局或者栅格系统,这时候可能需要像Tailwind CSS这样的工具来简化响应式设计。 接着,用户可能需要的不仅仅是基本的rem适配,还需要元素尺寸的动态缩放。这时候,使用JavaScript监听窗口变化,动态计算根字体大小可能有效。同时,像scale-box这样的组件可能对大屏适配有帮助,特别是在保持比例的情况下缩放内容。 另外,图片和媒体的适配也很重要,可能需要使用响应式图片,或者像v-lazy这样的懒加载插件来优化性能。视口单位(vw/vh)和百分比布局的结合使用可能更灵活,适应不同屏幕尺寸。 还要考虑第三方库的支持,比如lib-flexible或者postcss-px-to-viewport,这些工具可以自动转换单位,简化开发流程。同时,自定义指令可能在需要动态调整元素尺寸时派上用场,比如根据屏幕大小改变字体或间距。 最后,测试环节不可忽视,需要确保不同设备和屏幕尺寸下的显示效果一致,可能推荐使用浏览器开发者工具进行多设备模拟测试。 需要综合这些方法,提供一个分步骤的解决方案,涵盖基础配置、动态缩放、布局调整、第三方工具和测试建议。同时,要确保回答结构清晰,步骤明确,帮助用户逐步实现适配。</think>### Vue3移动端到大屏适配方案分步解析 在Vue3项目中实现移动端到大屏的适配,需要结合响应式布局与动态缩放策略。以下是经过验证的完整解决方案: #### 一、基础适配方案 1. **视口单位+rem混合方案** ```javascript // main.js const setRootFontSize = () => { const baseWidth = 375 // 以iPhone6/7/8为基准 const scale = document.documentElement.clientWidth / baseWidth document.documentElement.style.fontSize = `${Math.min(scale, 4) * 100}px` // 限制最大缩放比例 } window.addEventListener(&#39;resize&#39;, setRootFontSize) setRootFontSize() ``` ```css /* 使用混合单位 */ .container { padding: 0.2rem 0.1vw; /* 横向用vw保持流动性,纵向用rem保持比例 */ font-size: clamp(14px, 0.16rem + 0.5vw, 24px); /* 动态字体大小 */ } ``` 该方案结合了rem的等比缩放和vw的视口响应特性[^2] 2. **响应式断点配置** ```javascript // breakpoints.js export const BREAKPOINTS = { MOBILE: 375, TABLET: 768, DESKTOP: 1280, LARGE_SCREEN: 1920 } ``` #### 二、大屏适配增强方案 1. **动态缩放容器** ```vue <template> <div ref="scaleContainer" class="scale-wrapper"> <slot></slot> </div> </template> <script setup> import { ref, onMounted, onBeforeUnmount } from &#39;vue&#39; const scaleContainer = ref(null) const baseWidth = 1920 // 设计稿基准宽度 const updateScale = () => { const { clientWidth } = document.documentElement const scale = clientWidth / baseWidth scaleContainer.value.style.transform = `scale(${scale}) translateX(-50%)` scaleContainer.value.style.width = `${baseWidth}px` } onMounted(() => { window.addEventListener(&#39;resize&#39;, updateScale) updateScale() }) onBeforeUnmount(() => { window.removeEventListener(&#39;resize&#39;, updateScale) }) </script> <style> .scale-wrapper { position: fixed; left: 50%; top: 0; transform-origin: 0 0; transition: transform 0.3s; } </style> ``` 2. **栅格系统配置** ```css /* 响应式栅格 */ .grid-container { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: clamp(10px, 1vw, 30px); } @media (min-width: 1280px) { .grid-container { grid-template-columns: repeat(4, 1fr); } } ``` #### 三、进阶优化方案 1. **PostCSS自动转换** ```javascript // postcss.config.js module.exports = { plugins: { &#39;postcss-px-to-viewport&#39;: { unitToConvert: &#39;px&#39;, viewportWidth: 375, // 移动端基准 viewportHeight: 667, unitPrecision: 5, propList: [&#39;*&#39;], viewportUnit: &#39;vw&#39;, fontViewportUnit: &#39;vw&#39;, selectorBlackList: [], minPixelValue: 1, mediaQuery: false, replace: true, exclude: [/node_modules/], landscape: false, landscapeUnit: &#39;vw&#39;, landscapeWidth: 1136 } } } ``` 2. **混合单位策略** ```css /* 弹性布局示例 */ .card { width: min(90vw, 1200px); /* 限制最大宽度 */ height: calc(100vh - 60px); /* 结合视口单位 */ margin: 0 auto; padding: max(2vh, 20px); } ``` #### 四、实践建议 1. **设备方向处理** ```javascript // 横竖屏检测 const checkOrientation = () => { if (window.matchMedia("(orientation: portrait)").matches) { document.body.classList.add(&#39;portrait&#39;) document.body.classList.remove(&#39;landscape&#39;) } else { document.body.classList.add(&#39;landscape&#39;) document.body.classList.remove(&#39;portrait&#39;) } } ``` 2. **性能优化** ```vue <template> <img :src="responsiveImage.url" :srcset="responsiveImage.srcset" sizes="(max-width: 768px) 100vw, 50vw" alt="responsive image" > </template> <script setup> import { computed } from &#39;vue&#39; const props = defineProps({ imageSet: Object }) const responsiveImage = computed(() => ({ url: props.imageSet.mobile, srcset: ` ${props.imageSet.mobile} 375w, ${props.imageSet.tablet} 768w, ${props.imageSet.desktop} 1280w ` })) </script> ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流氓也是种气质 _Cookie

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值