PC 端屏幕适配三种方案

370 阅读4分钟

方案一: vw vh

配置vw vh转换函数

src/styles/common.scss

  • scss写法
@use "sass:math";

$vw_base: 1920;
$vh_base: 1080;

/* 计算vw */
@function vw($px) {
  @return math.div($px, $vw_base) * 100vw;
  // @return ($px / $vw_base) * 100vw;
}

/* 计算vh */
@function vh($px) {
  @return math.div($px, $vh_base) * 100vh;
  // @return ($px / $vh_base) * 100vh;
}
  • less写法
// 默认设计稿的宽度
@vw_base: 1920;

// 默认设计稿的高度
@vh_base: 1080;

.px2vw(@name, @px) {
  @{name}: (@px / @vw_base) * 100vw;
}

.px2vh(@name, @px) {
  @{name}: (@px / @vh_base) * 100vh;
}

.px2font(@px) {
  font-size: (@px / @vw_base) * 100vw;
}

vite 配置

在vite.config.js配置中引入全局css文件


export default defineConfig({
 css: {
        // css预处理器
        preprocessorOptions: {
            scss: {
                additionalData:
                    '@import "@/assets/styles/common.scss";'
            },
        },
    },

})

使用

scss

.box{
    width:vw(500);
}

less

.box{
    .px2vw(width, 500);
}

方案二: scale

这里 #app的内容进行缩放, 项目中的vh、vw单位要进行特殊处理(步骤2), 另外#app之外的元素如弹窗也要单独写样式(步骤3)

1. 缩放方法

import { ref, onBeforeUnmount, onMounted } from "vue";
import useSettingsStore from '@/store/modules/settings' // 屏幕设置参数
/**
 * 屏幕缩放
 */
export const useScreenScale = () => {
  // * 定时函数
  const timer = ref();
  const settingsStore = useSettingsStore()
  
  onMounted(() => {
    handleScreenScale();
    window.addEventListener("resize", resize);
  });
  onBeforeUnmount(() => {
    window.removeEventListener("resize", resize);
  });
  /**
   * 缩放防抖
   */
  const resize = () => {
    clearTimeout(timer.value);
    timer.value = setTimeout(() => {
      handleScreenScale();
    }, 200);
  };

  /**
   * 处理屏幕缩放
   */
  function handleScreenScale(designWidth = 1920, designHeight = 929) {
    const clientHeight = document.documentElement.clientHeight;
    const clientWidth = document.documentElement.clientWidth;
    const style = document.querySelector('#app').style
    let scale =  clientWidth < 600
        ? 1 : (clientWidth / clientHeight) < (designWidth / designHeight)
        ? (clientWidth / designWidth)
        : (clientHeight / designHeight);
    scale = Math.abs(1 - scale) > 0.1 ? scale.toFixed(2) : 1;
    scale = scale * settingsStore.screenSize; // screenSize几倍屏幕
    settingsStore.screenScale = scale; // 存储缩放值
    document.documentElement.style.setProperty(`--screenScale`,scale); // 存css变量到html
    const height = Math.round(clientHeight / scale);
    const width = Math.round(clientWidth / scale);
    style.height = `${height}px`;
    style.width = `${width}px`;
    style.transform = `scale(${scale})`;
    style.transformOrigin = `0 0`;
    document.body.style.overflow = `hidden`;
  }

};

2.写vite插件, 转化vh、vw单位

把所有文件中的vh、vw单位除以缩放比例var(--screenScale), 动态变更尺寸 /plugins/vw-vh-transform.ts

/**
 * vite插件, 把vw,vh除以缩放比例
 */
export default function vwVhTransform() {
  return {
    name: 'vw-vh-transform',
    transform(code, id) {
      const fileTypes = ['vue', 'js', 'ts', 'jsx', 'tsx', 'css', 'scss', 'less']
      // if (id.endsWith('.vue')) {
      if (fileTypes.includes(id.split('.').pop())) {
        // 匹配类似于 100vh的样式
        code = code.replace(/(?<![a-zA-Z])(\d*\.?\d+)vh(?!\s*\/\s*var\(--screenScale\))/g, 'calc($1vh / var(--screenScale))')
        code = code.replace(/(?<![a-zA-Z])(\d*\.?\d+)vw(?!\s*\/\s*var\(--screenScale\))/g, 'calc($1vw / var(--screenScale))')
        // 匹配类似于 calc(80vh - 110px) 的样式
        code = code.replace(/calc\((\d*\.?\d+)vh - (\d*\.?\d+)px\)/g, 'calc($1vh / var(--screenScale) - $2px)')
        code = code.replace(/calc\((\d*\.?\d+)vw - (\d*\.?\d+)px\)/g, 'calc($1vw / var(--screenScale) - $2px)')
      }
      return { code, map: null }
    }
  }
}

vite.config.ts使用

import vwVhTransform from './plugins/vw-vh-transform'

export default ({ mode, command }) => defineConfig({
  plugins: [
    vwVhTransform()
    ]
    })

3.app.vue使用

这里把 #app 之外的下拉菜单的样式做动态修改, 用v-bind失效, 所以用 var变量

利用zoom 缩放element-plus的弹窗

tooltip要特殊处理

#app 内的弹窗已经随app整体缩放过就不要缩放了, 设置zoom 为 1

el-drawer 不能设置append-to-body, 否则里面的弹窗位置会不对

<template>
    <router-view />
</template>
<script setup>
import { useScreenScale } from '@/utils/scale'
useScreenScale()
</script>
<style lang="scss">
.el-popper[role^=tooltip]:not(.is-pure):not(.el-popover) {
  padding: calc(var(--screenScale) * 5px) calc(var(--screenScale) * 11px) !important;
  font-size: calc(var(--screenScale) * 12px) !important;
  line-height: calc(var(--screenScale) * 20px) !important;
  min-width: calc(var(--screenScale) * 10px) !important;
}

.el-popover {
  transform: scale(var(--screenScale));
  transform-origin: 80% 0%;
}

.el-overlay-dialog {
    overflow: hidden !important;
    .el-dialog {
        transform: scale(var(--screenScale));
        width: calc(var(--el-dialog-width) / var(--screenScale)) !important;
        max-height: 100%;
        .el-dialog__body {
          max-height: calc(80vh / var(--screenScale));
          overflow-y: auto;
        }
    }
}

.el-notification,
.el-alert,
.el-loading-mask,
.el-message,
.el-message-box,
.el-drawer,
.el-menu--popup{
  zoom: var(--screenScale);
}


.el-popper:not(.el-popover) {
  .el-select-dropdown,
  .el-dropdown-menu,
  .el-popper__arrow,
  .el-picker-panel {
    zoom: var(--screenScale);
  }
}

#app {
  .el-popover {
    transform: scale(1);
  }
  .el-dialog {
    transform: scale(1);
    width: var(--el-dialog-width) !important;
  }

  .el-notification,
  .el-alert,
  .el-loading-mask,
  .el-message,
  .el-message-box,
  .el-drawer {
    zoom: 1;
  }


  .el-popper:not(.el-popover) {
    .el-select-dropdown,
    .el-dropdown-menu,
    .el-popper__arrow,
    .el-picker-panel,
    .el-menu--popup {
      zoom: 1;
    }
  }
}
</style>

方案三: rem

方式一 安装依赖

npm i postcss-pxtorem autoprefixer amfe-flexible --save-dev

postcss-pxtorem 是PostCSS的插件,用于将像素单元生成rem单位
autoprefixer 浏览器前缀处理插件
amfe-flexible 动态设置根元素字体大小, 替代了原先的lib-flexible 选用了当前众多浏览器兼容的viewport

创建 /postcss.config.js 配置文件, 配置完成后,页面内的 px 就会被转换成 rem

module.exports = {
	plugins: {
		autoprefixer: {
			overrideBrowserslist: [
				"Android 4.1",
				"iOS 7.1",
				"Chrome > 31",
				"ff > 31",
				"ie >= 8",
				"last 10 versions", // 所有主流浏览器最近10版本用
			],
			grid: true,
		},
		"postcss-pxtorem": {
			rootValue: 192, // 设计稿宽度的1/ 10 例如设计稿按照 1920设计 此处就为192
			propList: ["*", "!border"], // 除 border 外所有px 转 rem
			selectorBlackList: [".el-"], // 过滤掉.el-开头的class,不进行rem转换
		},
	},
};

main.js 引入依赖 import "amfe-flexible/index.js";

方式二

安装vscode插件 px to rem, 设置搜索px, 设置根节点大小, 输入px会自动转换rem

方式三

  1. 设置根节点大小 /style/reset.css
html {
  /* rem 相对于根元素 html 的fonts-size */
  /* 浏览器默认 16px * 62.5% = 10px, 1rem = 10px */
  font-size: 62.5%;
}
  1. 使用rem单位
div {
    font-size: 1.4rem;
}

方式二、三动态设置根元素fontSize大小

/utils/rem.js

(function flexible (window, document) {
  var docEl = document.documentElement
  var dpr = window.devicePixelRatio || 1

  // adjust body font size
  function setBodyFontSize () {
    if (document.body) {
      document.body.style.fontSize = (12 * dpr) + 'px'
    }
    else {
      document.addEventListener('DOMContentLoaded', setBodyFontSize)
    }
  }
  setBodyFontSize();

  // set 1rem = viewWidth / 10
  function setRemUnit () {
    var rem = docEl.clientWidth / 10
    docEl.style.fontSize = rem + 'px'
  }

  setRemUnit()

  // reset rem unit on page resize
  window.addEventListener('resize', setRemUnit)
  window.addEventListener('pageshow', function (e) {
    if (e.persisted) {
      setRemUnit()
    }
  })

  // detect 0.5px supports
  if (dpr >= 2) {
    var fakeBody = document.createElement('body')
    var testElement = document.createElement('div')
    testElement.style.border = '.5px solid transparent'
    fakeBody.appendChild(testElement)
    docEl.appendChild(fakeBody)
    if (testElement.offsetHeight === 1) {
      docEl.classList.add('hairlines')
    }
    docEl.removeChild(fakeBody)
  }
}(window, document))

或者 rem.js

// 基准大小
const baseSize = 16
// 设置 rem 函数
function setRem () {
  // 当前页面宽度相对于 1920 宽的缩放比例,可根据自己需要修改。
  const scale = document.documentElement.clientWidth / 1920
  // 设置页面根节点字体大小, 字体大小最小为12
  let fontSize = (baseSize * Math.min(scale, 2))>12 ? (baseSize * Math.min(scale, 2)): 12
  document.documentElement.style.fontSize = fontSize + 'px'
}
//初始化
setRem()
//改变窗口大小时重新设置 rem,这里最好加上节流
window.onresize = function () {
  setRem()
}

main.js 引入 import './utils/rem.js';