Vue 移动端适配:rem/vw单位与flexible.js

举报
William 发表于 2025/11/14 09:37:12 2025/11/14
【摘要】 一、引言1.1 移动端适配的重要性在移动互联网时代,移动设备已成为主要的互联网接入方式。据统计,2024年全球移动互联网用户已超过50亿,移动端流量占比超过60%。面对碎片化的屏幕尺寸和多样化的设备分辨率,移动端适配成为前端开发的核心挑战。1.2 移动端适配的技术演进class MobileAdaptationEvolution { static getDevelopmentStage...


一、引言

1.1 移动端适配的重要性

移动互联网时代,移动设备已成为主要的互联网接入方式。据统计,2024年全球移动互联网用户已超过50亿,移动端流量占比超过60%。面对碎片化的屏幕尺寸多样化的设备分辨率,移动端适配成为前端开发的核心挑战。

1.2 移动端适配的技术演进

class MobileAdaptationEvolution {
    static getDevelopmentStages() {
        return {
            '1.0 时代': '固定宽度布局,简单媒体查询',
            '2.0 时代': '流式布局,百分比单位',
            '3.0 时代': '响应式设计,弹性布局',
            '4.0 时代': 'REM/VW单位,Viewport适配',
            '5.0 时代': '容器查询,智能适配'
        };
    }

    static getTechnologyComparison() {
        return {
            'REM 适配': {
                '兼容性': '⭐⭐⭐⭐⭐',
                '精确度': '⭐⭐⭐⭐',
                '开发效率': '⭐⭐⭐⭐',
                '维护成本': '⭐⭐⭐',
                '性能表现': '⭐⭐⭐⭐'
            },
            'VW 适配': {
                '兼容性': '⭐⭐⭐⭐',
                '精确度': '⭐⭐⭐⭐⭐',
                '开发效率': '⭐⭐⭐⭐⭐',
                '维护成本': '⭐⭐',
                '性能表现': '⭐⭐⭐⭐⭐'
            },
            'Flexible.js': {
                '兼容性': '⭐⭐⭐⭐⭐',
                '精确度': '⭐⭐⭐⭐',
                '开发效率': '⭐⭐⭐⭐',
                '维护成本': '⭐⭐⭐',
                '性能表现': '⭐⭐⭐⭐'
            }
        };
    }
}

1.3 市场数据支持

指标
数据
趋势
对适配的影响
移动设备尺寸
4-7英寸为主,折叠屏增长
多样化
需要更精细的适配方案
屏幕分辨率
720P-4K,PPI持续提升
高密度
需要视网膜屏适配
设备比例
16:9, 18:9, 19.5:9, 21:9
异形化
需要安全区域处理
网络环境
4G普及,5G增长
高速化
可承载更复杂适配逻辑

二、技术背景

2.1 核心概念解析

2.1.1 REM单位原理

// REM(Root EM)基于根元素字体大小的相对单位
class REMUnit {
    constructor() {
        this.baseFontSize = 16; // 默认根字体大小(px)
    }
    
    // 计算REM值
    calculateREM(pixelValue) {
        return pixelValue / this.baseFontSize;
    }
    
    // 设置根字体大小
    setRootFontSize(newSize) {
        document.documentElement.style.fontSize = newSize + 'px';
        this.baseFontSize = newSize;
    }
    
    // 响应式调整
    responsiveAdjustment(designWidth = 750) {
        const clientWidth = document.documentElement.clientWidth;
        const scale = clientWidth / designWidth;
        this.setRootFontSize(scale * 100); // 1rem = 100px(设计稿基准)
    }
}

2.1.2 VW单位原理

// VW(Viewport Width)基于视口宽度的相对单位
class VWUnit {
    // 计算VW值
    calculateVW(pixelValue, designWidth = 750) {
        return (pixelValue / designWidth) * 100;
    }
    
    // VW到PX的转换
    vwToPx(vwValue, viewportWidth) {
        return (vwValue / 100) * viewportWidth;
    }
    
    // 处理VW的兼容性
    handleCompatibility() {
        // 检测浏览器支持情况
        const isSupported = CSS.supports('width', '1vw');
        if (!isSupported) {
            this.applyFallback();
        }
    }
    
    applyFallback() {
        // 使用JavaScript polyfill
        window.addEventListener('resize', this.vwPolyfill);
    }
}

2.1.3 Flexible.js工作机制

// Flexible.js 核心实现原理
class Flexible {
    constructor() {
        this.dpr = window.devicePixelRatio || 1;
        this.docEl = document.documentElement;
        this.metaEl = document.querySelector('meta[name="viewport"]');
        this.init();
    }
    
    init() {
        this.setViewport();
        this.setBodyFontSize();
        this.setRemUnit();
        this.bindEvents();
    }
    
    // 设置Viewport
    setViewport() {
        if (this.metaEl) {
            this.metaEl.setAttribute('content', 
                `width=device-width, initial-scale=${1/this.dpr}, maximum-scale=${1/this.dpr}, minimum-scale=${1/this.dpr}, user-scalable=no`);
        } else {
            const meta = document.createElement('meta');
            meta.setAttribute('name', 'viewport');
            meta.setAttribute('content', 
                `width=device-width, initial-scale=${1/this.dpr}, maximum-scale=${1/this.dpr}, minimum-scale=${1/this.dpr}, user-scalable=no`);
            document.head.appendChild(meta);
        }
    }
    
    // 设置REM基准
    setRemUnit() {
        const width = this.docEl.clientWidth;
        const rem = width / 10; // 将屏幕分成10份
        this.docEl.style.fontSize = rem + 'px';
    }
    
    // 事件绑定
    bindEvents() {
        window.addEventListener('resize', () => {
            clearTimeout(this.resizeTimer);
            this.resizeTimer = setTimeout(() => this.setRemUnit(), 300);
        });
        
        window.addEventListener('pageshow', (e) => {
            if (e.persisted) this.setRemUnit();
        });
    }
    
    // 设置Body字体大小(防止继承影响)
    setBodyFontSize() {
        if (document.body) {
            document.body.style.fontSize = (12 * this.dpr) + 'px';
        } else {
            document.addEventListener('DOMContentLoaded', this.setBodyFontSize);
        }
    }
}

2.2 技术对比分析

2.2.1 单位特性对比表

特性
REM
VW
百分比
PX
相对基准
根元素字体
视口宽度
父元素
绝对单位
响应式
⭐⭐⭐⭐⭐
⭐⭐⭐⭐⭐
⭐⭐⭐
兼容性
⭐⭐⭐⭐⭐
⭐⭐⭐⭐
⭐⭐⭐⭐⭐
⭐⭐⭐⭐⭐
精确度
⭐⭐⭐⭐
⭐⭐⭐⭐⭐
⭐⭐⭐
⭐⭐⭐⭐⭐
维护性
⭐⭐⭐
⭐⭐⭐⭐⭐
⭐⭐⭐⭐
⭐⭐

2.2.2 应用场景分析

class AdaptationScenario {
    static getBestPractices() {
        return {
            '电商网站': {
                '推荐方案': 'REM + Flexible.js',
                '理由': '需要精确控制元素尺寸,兼容老设备',
                '实现复杂度': '中等'
            },
            '管理系统': {
                '推荐方案': 'VW + 媒体查询',
                '理由': '现代浏览器支持好,开发效率高',
                '实现复杂度': '低'
            },
            '移动H5': {
                '推荐方案': 'REM + VW 混合',
                '理由': '兼顾兼容性和开发体验',
                '实现复杂度': '高'
            },
            '跨端应用': {
                '推荐方案': '响应式 + 容器查询',
                '理由': '需要适应多种容器环境',
                '实现复杂度': '高'
            }
        };
    }
    
    static getPerformanceMetrics() {
        return {
            'REM方案': {
                '加载时间': '100-200ms',
                '渲染性能': '优秀',
                '内存占用': '低',
                'CPU使用率': '低'
            },
            'VW方案': {
                '加载时间': '50-100ms',
                '渲染性能': '优秀',
                '内存占用': '极低',
                'CPU使用率': '极低'
            },
            'Flexible.js': {
                '加载时间': '150-250ms',
                '渲染性能': '良好',
                '内存占用': '中等',
                'CPU使用率': '中等'
            }
        };
    }
}

三、环境准备与配置

3.1 项目初始化配置

3.1.1 Vue项目创建与配置

# 创建Vue项目
vue create mobile-adaptation-demo

# 安装必要依赖
npm install postcss-px-to-viewport postcss-rem-flexible amfe-flexible
npm install lib-flexible -S

3.1.2 项目结构规划

src/
├── assets/
│   ├── styles/
│   │   ├── adaption/          # 适配相关样式
│   │   │   ├── rem.scss       # REM适配配置
│   │   │   ├── vw.scss        # VW适配配置
│   │   │   └── mixins.scss    # 适配混合器
│   │   └── base/
│   │       ├── reset.scss     # 样式重置
│   │       └── variables.scss # 样式变量
├── utils/
│   ├── adaption.js           # 适配工具函数
│   └── flexible.js          # Flexible.js封装
└── views/
    ├── rem-demo.vue         # REM适配示例
    ├── vw-demo.vue          # VW适配示例
    └── flexible-demo.vue    # Flexible.js示例

3.2 三种适配方案的详细配置

3.2.1 REM适配方案配置

// postcss.config.js - REM配置
module.exports = {
  plugins: {
    'postcss-pxtorem': {
      rootValue: 16, // 基准字体大小
      unitPrecision: 5, // 转换精度
      propList: ['*'], // 需要转换的属性
      selectorBlackList: [], // 忽略的选择器
      replace: true,
      mediaQuery: false,
      minPixelValue: 2, // 最小转换值
      exclude: /node_modules/i
    }
  }
}
// src/assets/styles/adaption/_rem.scss
// REM适配核心样式
:root {
  // 基准字体大小设置
  font-size: 16px;
  
  @media screen and (min-width: 375px) {
    font-size: calc(16px + 2 * (100vw - 375px) / 39); // 375px-414px适配
  }
  
  @media screen and (min-width: 414px) {
    font-size: 18px; // 414px基准
  }
  
  @media screen and (min-width: 768px) {
    font-size: 20px; // 平板设备
  }
}

// REM工具类
.rem-container {
  width: 100%;
  max-width: 20rem; // 320px
  margin: 0 auto;
  padding: 0 1rem; // 16px
}

.rem-button {
  height: 2.5rem; // 40px
  padding: 0 1.5rem; // 24px
  font-size: 0.875rem; // 14px
  border-radius: 0.25rem; // 4px
}

// REM响应式混合器
@mixin rem-responsive($property, $values...) {
  #{$property}: $values;
  
  // 针对不同屏幕的REM适配
  @media screen and (max-width: 320px) {
    #{$property}: $values * 0.9;
  }
  
  @media screen and (min-width: 414px) and (max-width: 768px) {
    #{$property}: $values * 1.1;
  }
}

3.2.2 VW适配方案配置

// postcss.config.js - VW配置
module.exports = {
  plugins: {
    'postcss-px-to-viewport': {
      viewportWidth: 375, // 设计稿宽度
      viewportHeight: 667, // 设计稿高度
      unitPrecision: 3, // 转换精度
      viewportUnit: 'vw', // 转换单位
      selectorBlackList: ['.ignore', '.hairlines'], // 忽略类名
      minPixelValue: 1, // 最小转换值
      mediaQuery: false, // 媒体查询中的px是否转换
      exclude: /(\/|\\)(node_modules)(\/|\\)/
    }
  }
}
// src/assets/styles/adaption/_vw.scss
// VW适配核心样式
:root {
  // VW基准设置
  --vw-base: 3.75vw; // 基于375px设计稿
}

// VW容器类
.vw-container {
  width: 100vw;
  max-width: 100vw;
  margin: 0 auto;
}

// VW工具函数
@function vw($px) {
  @return ($px / 3.75) * 1vw;
}

@function vh($px) {
  @return ($px / 6.67) * 1vh;
}

// VW组件样式
.vw-button {
  width: vw(345);
  height: vw(44);
  padding: vw(12) vw(16);
  font-size: vw(16);
  border-radius: vw(8);
}

// 安全区域适配
.safe-area {
  padding-left: constant(safe-area-inset-left);
  padding-right: constant(safe-area-inset-right);
  padding-bottom: constant(safe-area-inset-bottom);
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
  padding-bottom: env(safe-area-inset-bottom);
}

3.2.3 Flexible.js方案配置

// src/utils/flexible.js
(function flexible(window, document) {
  const docEl = document.documentElement;
  const dpr = window.devicePixelRatio || 1;
  
  // 调整body字体大小
  function setBodyFontSize() {
    if (document.body) {
      document.body.style.fontSize = (12 * dpr) + 'px';
    } else {
      document.addEventListener('DOMContentLoaded', setBodyFontSize);
    }
  }
  setBodyFontSize();
  
  // 设置1rem = viewWidth / 10
  function setRemUnit() {
    const rem = docEl.clientWidth / 10;
    docEl.style.fontSize = rem + 'px';
  }
  
  setRemUnit();
  
  // 监听页面重置大小
  window.addEventListener('resize', setRemUnit);
  window.addEventListener('pageshow', function(e) {
    if (e.persisted) {
      setRemUnit();
    }
  });
  
  // 检测0.5px支持
  if (dpr >= 2) {
    const fakeBody = document.createElement('body');
    const 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));
// src/assets/styles/adaption/_flexible.scss
// Flexible.js适配样式
[data-dpr="1"] .dpr-text {
  font-size: 12px;
}

[data-dpr="2"] .dpr-text {
  font-size: 24px;
}

[data-dpr="3"] .dpr-text {
  font-size: 36px;
}

// 1px边框解决方案
.hairlines {
  .border-1px {
    position: relative;
    
    &::after {
      content: "";
      position: absolute;
      bottom: 0;
      left: 0;
      width: 100%;
      height: 1px;
      background: #e5e5e5;
      transform: scaleY(0.5);
      transform-origin: 0 0;
    }
  }
}

// Flexible.js工具类
.flexible-container {
  width: 10rem; // 满屏宽度
  margin: 0 auto;
}

.px2rem(@name, @px) {
  @{name}: @px / 75 * 1rem;
}

四、详细代码实现

4.1 REM适配完整示例

4.1.1 REM配置工具类

// src/utils/rem-adaption.js
export class REMAdaption {
  constructor(designWidth = 750, maxWidth = 768) {
    this.designWidth = designWidth;
    this.maxWidth = maxWidth;
    this.init();
  }
  
  init() {
    this.setRemUnit();
    this.bindEvents();
  }
  
  // 设置REM基准
  setRemUnit() {
    const docEl = document.documentElement;
    const clientWidth = Math.min(docEl.clientWidth, this.maxWidth);
    const rem = (clientWidth / this.designWidth) * 100;
    
    docEl.style.fontSize = rem + 'px';
    
    // 设置实际字体大小(防止过大)
    const actualSize = parseFloat(getComputedStyle(docEl).fontSize);
    if (actualSize !== rem) {
      docEl.style.fontSize = (rem * rem / actualSize) + 'px';
    }
  }
  
  // 事件绑定
  bindEvents() {
    let timer = null;
    window.addEventListener('resize', () => {
      clearTimeout(timer);
      timer = setTimeout(() => this.setRemUnit(), 300);
    });
    
    window.addEventListener('pageshow', (e) => {
      if (e.persisted) this.setRemUnit();
    });
  }
  
  // PX转REM工具函数
  static px2rem(px, designWidth = 750) {
    return (px / designWidth) * 100 + 'rem';
  }
  
  // REM转PX工具函数
  static rem2px(rem, designWidth = 750) {
    return (rem / 100) * designWidth + 'px';
  }
}

// 初始化REM适配
export const initREM = (designWidth = 750) => {
  return new REMAdaption(designWidth);
};

4.1.2 REM适配Vue组件

<!-- src/views/RemDemo.vue -->
<template>
  <div class="rem-demo-container">
    <!-- 头部导航 -->
    <header class="rem-header">
      <div class="rem-nav">
        <h1 class="rem-title">REM适配演示</h1>
        <nav class="rem-nav-menu">
          <a v-for="item in navItems" 
             :key="item.id" 
             :class="['rem-nav-item', { active: activeNav === item.id }]"
             @click="activeNav = item.id">
            {{ item.text }}
          </a>
        </nav>
      </div>
    </header>
    
    <!-- 主要内容区域 -->
    <main class="rem-main">
      <!-- 响应式网格布局 -->
      <div class="rem-grid">
        <div v-for="card in cardList" 
             :key="card.id" 
             class="rem-card"
             :style="{ backgroundColor: card.color }">
          <div class="rem-card-header">
            <h3>{{ card.title }}</h3>
            <span class="rem-badge">{{ card.badge }}</span>
          </div>
          <div class="rem-card-content">
            <p>{{ card.content }}</p>
          </div>
          <div class="rem-card-footer">
            <button class="rem-button primary">查看详情</button>
            <button class="rem-button secondary">收藏</button>
          </div>
        </div>
      </div>
      
      <!-- 响应式表格 -->
      <div class="rem-table-container">
        <table class="rem-table">
          <thead>
            <tr>
              <th v-for="col in tableColumns" :key="col.key">{{ col.title }}</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="row in tableData" :key="row.id">
              <td v-for="col in tableColumns" :key="col.key">{{ row[col.key] }}</td>
            </tr>
          </tbody>
        </table>
      </div>
    </main>
    
    <!-- 底部信息 -->
    <footer class="rem-footer">
      <p>&copy; 2024 REM适配演示. 所有尺寸均使用REM单位</p>
    </footer>
  </div>
</template>

<script>
import { initREM } from '@/utils/rem-adaption';

export default {
  name: 'RemDemo',
  data() {
    return {
      activeNav: 'home',
      navItems: [
        { id: 'home', text: '首页' },
        { id: 'products', text: '产品' },
        { id: 'about', text: '关于' },
        { id: 'contact', text: '联系' }
      ],
      cardList: [
        {
          id: 1,
          title: '响应式卡片1',
          content: '这是一个使用REM单位适配的卡片组件',
          badge: 'New',
          color: '#f0f8ff'
        },
        {
          id: 2,
          title: '响应式卡片2',
          content: '在不同设备上保持一致的视觉效果',
          badge: 'Hot',
          color: '#fff0f5'
        },
        {
          id: 3,
          title: '响应式卡片3',
          content: '基于根字体大小的相对单位计算',
          badge: '推荐',
          color: '#f5f5f5'
        }
      ],
      tableColumns: [
        { key: 'name', title: '名称' },
        { key: 'size', title: '尺寸(REM)' },
        { key: 'actual', title: '实际像素' }
      ],
      tableData: [
        { id: 1, name: '按钮高度', size: '2.5rem', actual: '40px' },
        { id: 2, name: '卡片内边距', size: '1rem', actual: '16px' },
        { id: 3, name: '字体大小', size: '0.875rem', actual: '14px' }
      ]
    };
  },
  mounted() {
    // 初始化REM适配
    initREM(750);
    
    // 监听窗口变化
    this.handleResize();
    window.addEventListener('resize', this.handleResize);
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.handleResize);
  },
  methods: {
    handleResize() {
      // 可以在这里添加特定的响应式逻辑
      console.log('窗口尺寸变化,当前REM基准:', getComputedStyle(document.documentElement).fontSize);
    }
  }
};
</script>

<style lang="scss" scoped>
// 引入REM适配样式
@import '@/assets/styles/adaption/rem.scss';

.rem-demo-container {
  min-height: 100vh;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}

.rem-header {
  background: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid rgba(255, 255, 255, 0.2);
  
  .rem-nav {
    @include rem-responsive(padding, 0 1rem);
    @include rem-responsive(height, 3rem);
    display: flex;
    justify-content: space-between;
    align-items: center;
    max-width: 20rem;
    margin: 0 auto;
    
    .rem-title {
      @include rem-responsive(font-size, 1.125rem);
      font-weight: 600;
      color: #333;
      margin: 0;
    }
    
    .rem-nav-menu {
      display: flex;
      gap: 1.5rem;
      
      .rem-nav-item {
        @include rem-responsive(padding, 0.5rem 1rem);
        @include rem-responsive(font-size, 0.875rem);
        color: #666;
        text-decoration: none;
        border-radius: 0.5rem;
        transition: all 0.3s ease;
        cursor: pointer;
        
        &:hover, &.active {
          background: #667eea;
          color: white;
        }
      }
    }
  }
}

.rem-main {
  @include rem-responsive(padding, 2rem 1rem);
  max-width: 20rem;
  margin: 0 auto;
}

.rem-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  gap: 1rem;
  margin-bottom: 2rem;
  
  .rem-card {
    @include rem-responsive(padding, 1.5rem);
    border-radius: 0.75rem;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    border: 1px solid rgba(255, 255, 255, 0.2);
    
    .rem-card-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 1rem;
      
      h3 {
        @include rem-responsive(font-size, 1rem);
        margin: 0;
        color: #333;
      }
      
      .rem-badge {
        @include rem-responsive(padding, 0.25rem 0.5rem);
        @include rem-responsive(font-size, 0.75rem);
        background: #ff4757;
        color: white;
        border-radius: 1rem;
      }
    }
    
    .rem-card-content {
      @include rem-responsive(margin-bottom, 1rem);
      
      p {
        @include rem-responsive(font-size, 0.875rem);
        color: #666;
        line-height: 1.5;
        margin: 0;
      }
    }
    
    .rem-card-footer {
      display: flex;
      gap: 0.5rem;
      
      .rem-button {
        @include rem-responsive(padding, 0.5rem 1rem);
        @include rem-responsive(font-size, 0.875rem);
        border: none;
        border-radius: 0.5rem;
        cursor: pointer;
        transition: all 0.3s ease;
        
        &.primary {
          background: #667eea;
          color: white;
          
          &:hover {
            background: #5a6fd8;
          }
        }
        
        &.secondary {
          background: transparent;
          color: #667eea;
          border: 1px solid #667eea;
          
          &:hover {
            background: #667eea;
            color: white;
          }
        }
      }
    }
  }
}

.rem-table-container {
  overflow-x: auto;
  background: white;
  border-radius: 0.75rem;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  
  .rem-table {
    width: 100%;
    border-collapse: collapse;
    
    th, td {
      @include rem-responsive(padding, 1rem);
      text-align: left;
      border-bottom: 1px solid #e5e5e5;
    }
    
    th {
      @include rem-responsive(font-size, 0.875rem);
      font-weight: 600;
      color: #333;
      background: #f8f9fa;
    }
    
    td {
      @include rem-responsive(font-size, 0.875rem);
      color: #666;
    }
    
    tr:last-child td {
      border-bottom: none;
    }
  }
}

.rem-footer {
  @include rem-responsive(padding, 1.5rem);
  text-align: center;
  background: rgba(255, 255, 255, 0.95);
  border-top: 1px solid rgba(255, 255, 255, 0.2);
  
  p {
    @include rem-responsive(font-size, 0.875rem);
    color: #666;
    margin: 0;
  }
}

// 响应式媒体查询
@media (max-width: 768px) {
  .rem-grid {
    grid-template-columns: 1fr;
  }
  
  .rem-nav-menu {
    gap: 0.5rem !important;
    
    .rem-nav-item {
      padding: 0.25rem 0.5rem !important;
    }
  }
}

@media (max-width: 480px) {
  .rem-nav {
    flex-direction: column;
    height: auto !important;
    padding: 1rem !important;
    
    .rem-nav-menu {
      margin-top: 1rem;
      flex-wrap: wrap;
      justify-content: center;
    }
  }
}
</style>

4.2 VW适配完整示例

4.2.1 VW配置工具类

// src/utils/vw-adaption.js
export class VWAdaption {
  constructor(designWidth = 375, designHeight = 667) {
    this.designWidth = designWidth;
    this.designHeight = designHeight;
    this.init();
  }
  
  init() {
    this.setViewportMeta();
    this.handleOrientationChange();
  }
  
  // 设置Viewport Meta
  setViewportMeta() {
    let viewport = document.querySelector('meta[name="viewport"]');
    if (!viewport) {
      viewport = document.createElement('meta');
      viewport.name = 'viewport';
      document.head.appendChild(viewport);
    }
    
    viewport.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';
  }
  
  // 处理横竖屏切换
  handleOrientationChange() {
    window.addEventListener('orientationchange', () => {
      setTimeout(() => {
        this.fixViewportScale();
      }, 300);
    });
  }
  
  // 修复视口缩放问题
  fixViewportScale() {
    const viewport = document.querySelector('meta[name="viewport"]');
    if (viewport) {
      viewport.content = 'width=device-width, initial-scale=1.0';
    }
  }
  
  // PX转VW工具函数
  static px2vw(px, designWidth = 375) {
    return (px / designWidth) * 100 + 'vw';
  }
  
  // PX转VH工具函数
  static px2vh(px, designHeight = 667) {
    return (px / designHeight) * 100 + 'vh';
  }
  
  // VW转PX工具函数
  static vw2px(vw, viewportWidth) {
    return (parseFloat(vw) / 100) * viewportWidth + 'px';
  }
  
  // 安全区域适配
  static safeArea(property, position = 'bottom') {
    return `
      ${property}: constant(safe-area-inset-${position});
      ${property}: env(safe-area-inset-${position});
    `;
  }
}

// CSS-in-JS风格的VW适配函数
export const createVWStyle = (styles, designWidth = 375) => {
  const convertedStyles = {};
  
  Object.keys(styles).forEach(key => {
    const value = styles[key];
    
    if (typeof value === 'number' && key !== 'zIndex' && key !== 'opacity') {
      // 转换数值为VW
      convertedStyles[key] = (value / designWidth) * 100 + 'vw';
    } else if (typeof value === 'string' && /^\d+px$/.test(value)) {
      // 转换px字符串为VW
      const pxValue = parseFloat(value);
      convertedStyles[key] = (pxValue / designWidth) * 100 + 'vw';
    } else {
      convertedStyles[key] = value;
    }
  });
  
  return convertedStyles;
};

4.2.2 VW适配Vue组件

<!-- src/views/VwDemo.vue -->
<template>
  <div class="vw-demo-container safe-area">
    <!-- 沉浸式头部 -->
    <header class="vw-header">
      <div class="vw-header-content">
        <button class="vw-back-button" @click="handleBack">
          <span class="vw-icon">←</span>
        </button>
        <h1 class="vw-title">VW适配演示</h1>
        <button class="vw-menu-button" @click="showMenu = !showMenu">
          <span class="vw-icon">⋮</span>
        </button>
      </div>
      
      <!-- 下拉菜单 -->
      <transition name="vw-menu-slide">
        <div v-show="showMenu" class="vw-dropdown-menu">
          <div v-for="item in menuItems" 
               :key="item.id" 
               class="vw-menu-item"
               @click="handleMenuClick(item)">
            <span class="vw-menu-icon">{{ item.icon }}</span>
            <span class="vw-menu-text">{{ item.text }}</span>
          </div>
        </div>
      </transition>
    </header>
    
    <!-- 可滚动内容区域 -->
    <main class="vw-main">
      <!-- 轮播图 -->
      <section class="vw-carousel">
        <div class="vw-carousel-track" 
             :style="{ transform: `translateX(-${currentSlide * 100}%)` }">
          <div v-for="(slide, index) in carouselSlides" 
               :key="index" 
               class="vw-carousel-slide"
               :style="{ backgroundImage: `url(${slide.image})` }">
            <div class="vw-slide-content">
              <h3>{{ slide.title }}</h3>
              <p>{{ slide.description }}</p>
            </div>
          </div>
        </div>
        
        <!-- 指示器 -->
        <div class="vw-carousel-indicators">
          <span v-for="(_, index) in carouselSlides" 
                :key="index"
                :class="['vw-indicator', { active: index === currentSlide }]"
                @click="currentSlide = index">
          </span>
        </div>
      </section>
      
      <!-- 功能卡片网格 -->
      <section class="vw-feature-grid">
        <h2 class="vw-section-title">功能特性</h2>
        <div class="vw-grid">
          <div v-for="feature in features" 
               :key="feature.id" 
               class="vw-feature-card"
               @click="handleFeatureClick(feature)">
            <div class="vw-feature-icon" :style="{ backgroundColor: feature.color }">
              {{ feature.icon }}
            </div>
            <h4>{{ feature.title }}</h4>
            <p>{{ feature.description }}</p>
          </div>
        </div>
      </section>
      
      <!-- 数据统计 -->
      <section class="vw-stats-section">
        <h2 class="vw-section-title">性能统计</h2>
        <div class="vw-stats-grid">
          <div v-for="stat in stats" 
               :key="stat.id" 
               class="vw-stat-item">
            <div class="vw-stat-value">{{ stat.value }}</div>
            <div class="vw-stat-label">{{ stat.label }}</div>
          </div>
        </div>
      </section>
    </main>
    
    <!-- 底部导航 -->
    <footer class="vw-footer safe-area">
      <nav class="vw-bottom-nav">
        <div v-for="nav in bottomNav" 
             :key="nav.id" 
             :class="['vw-nav-item', { active: activeNav === nav.id }]"
             @click="activeNav = nav.id">
          <span class="vw-nav-icon">{{ nav.icon }}</span>
          <span class="vw-nav-text">{{ nav.text }}</span>
        </div>
      </nav>
    </footer>
  </div>
</template>

<script>
import { VWAdaption, createVWStyle } from '@/utils/vw-adaption';

export default {
  name: 'VwDemo',
  data() {
    return {
      showMenu: false,
      currentSlide: 0,
      activeNav: 'home',
      carouselSlides: [
        {
          image: 'https://picsum.photos/375/200?random=1',
          title: 'VW单位适配',
          description: '基于视口宽度的响应式设计'
        },
        {
          image: 'https://picsum.photos/375/200?random=2',
          title: '完美比例',
          description: '在任何设备上保持设计比例'
        },
        {
          image: 'https://picsum.photos/375/200?random=3',
          title: '高性能',
          description: '原生CSS单位,渲染性能优秀'
        }
      ],
      menuItems: [
        { id: 'settings', icon: '⚙️', text: '设置' },
        { id: 'profile', icon: '👤', text: '个人资料' },
        { id: 'help', icon: '❓', text: '帮助中心' },
        { id: 'feedback', icon: '💬', text: '意见反馈' }
      ],
      features: [
        {
          id: 1,
          icon: '📱',
          title: '响应式设计',
          description: '完美适配各种移动设备',
          color: '#ff6b6b'
        },
        {
          id: 2,
          title: '高性能渲染',
          description: '基于VW单位的优化渲染',
          icon: '⚡',
          color: '#4ecdc4'
        },
        {
          id: 3,
          icon: '🎨',
          title: '设计一致性',
          description: '保持设计稿原始比例',
          color: '#45b7d1'
        },
        {
          id: 4,
          icon: '🔧',
          title: '易于维护',
          description: '简洁的适配方案',
          color: '#96ceb4'
        }
      ],
      stats: [
        { id: 1, value: '100%', label: '适配精度' },
        { id: 2, value: '60FPS', label: '渲染帧率' },
        { id: 3, value: '<100ms', label: '加载时间' },
        { id: 4, value: '99.9%', label: '兼容性' }
      ],
      bottomNav: [
        { id: 'home', icon: '🏠', text: '首页' },
        { id: 'explore', icon: '🔍', text: '发现' },
        { id: 'cart', icon: '🛒', text: '购物车' },
        { id: 'profile', icon: '👤', text: '我的' }
      ]
    };
  },
  mounted() {
    // 初始化VW适配
    new VWAdaption(375, 667);
    
    // 自动轮播
    this.startAutoPlay();
    
    // 监听横竖屏切换
    this.handleOrientation();
  },
  beforeDestroy() {
    this.stopAutoPlay();
  },
  methods: {
    startAutoPlay() {
      this.autoPlayTimer = setInterval(() => {
        this.currentSlide = (this.currentSlide + 1) % this.carouselSlides.length;
      }, 3000);
    },
    
    stopAutoPlay() {
      if (this.autoPlayTimer) {
        clearInterval(this.autoPlayTimer);
      }
    },
    
    handleOrientation() {
      window.addEventListener('orientationchange', () => {
        // 横竖屏切换时重新计算布局
        setTimeout(() => {
          this.$forceUpdate();
        }, 300);
      });
    },
    
    handleBack() {
      this.$router.back();
    },
    
    handleMenuClick(item) {
      this.showMenu = false;
      console.log('菜单点击:', item);
    },
    
    handleFeatureClick(feature) {
      console.log('功能点击:', feature);
    }
  }
};
</script>

<style lang="scss" scoped>
// 引入VW适配样式
@import '@/assets/styles/adaption/vw.scss';

.vw-demo-container {
  min-height: 100vh;
  background: #f5f5f5;
  display: flex;
  flex-direction: column;
}

.vw-header {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  position: relative;
  
  .vw-header-content {
    @include vw-responsive(padding, 12px 16px);
    display: flex;
    align-items: center;
    justify-content: space-between;
    
    .vw-back-button, .vw-menu-button {
      @include vw-responsive(width, 44px);
      @include vw-responsive(height, 44px);
      background: rgba(255, 255, 255, 0.2);
      border: none;
      border-radius: 50%;
      color: white;
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: all 0.3s ease;
      
      &:hover {
        background: rgba(255, 255, 255, 0.3);
      }
      
      .vw-icon {
        @include vw-responsive(font-size, 18px);
      }
    }
    
    .vw-title {
      @include vw-responsive(font-size, 18px);
      font-weight: 600;
      margin: 0;
    }
  }
  
  .vw-dropdown-menu {
    background: white;
    border-radius: 0 0 8px 8px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    overflow: hidden;
    
    .vw-menu-item {
      @include vw-responsive(padding, 12px 16px);
      display: flex;
      align-items: center;
      cursor: pointer;
      transition: background-color 0.3s ease;
      
      &:hover {
        background: #f5f5f5;
      }
      
      .vw-menu-icon {
        @include vw-responsive(margin-right, 12px);
        @include vw-responsive(font-size, 18px);
      }
      
      .vw-menu-text {
        @include vw-responsive(font-size, 14px);
        color: #333;
      }
    }
  }
}

.vw-main {
  flex: 1;
  overflow-y: auto;
}

.vw-carousel {
  position: relative;
  @include vw-responsive(height, 200px);
  overflow: hidden;
  border-radius: 0 0 16px 16px;
  
  .vw-carousel-track {
    display: flex;
    transition: transform 0.5s ease;
    height: 100%;
    
    .vw-carousel-slide {
      flex-shrink: 0;
      width: 100%;
      height: 100%;
      background-size: cover;
      background-position: center;
      position: relative;
      
      &::before {
        content: '';
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.3);
      }
      
      .vw-slide-content {
        position: absolute;
        bottom: 0;
        left: 0;
        right: 0;
        @include vw-responsive(padding, 20px 16px);
        color: white;
        z-index: 1;
        
        h3 {
          @include vw-responsive(font-size, 18px);
          @include vw-responsive(margin-bottom, 8px);
          font-weight: 600;
        }
        
        p {
          @include vw-responsive(font-size, 14px);
          margin: 0;
          opacity: 0.9;
        }
      }
    }
  }
  
  .vw-carousel-indicators {
    position: absolute;
    bottom: 12px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    gap: 8px;
    
    .vw-indicator {
      @include vw-responsive(width, 8px);
      @include vw-responsive(height, 8px);
      background: rgba(255, 255, 255, 0.5);
      border-radius: 50%;
      cursor: pointer;
      transition: all 0.3s ease;
      
      &.active {
        background: white;
        @include vw-responsive(width, 20px);
        border-radius: 4px;
      }
    }
  }
}

.vw-feature-grid {
  @include vw-responsive(padding, 20px 16px);
  
  .vw-section-title {
    @include vw-responsive(font-size, 20px);
    @include vw-responsive(margin-bottom, 16px);
    font-weight: 600;
    color: #333;
  }
  
  .vw-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 12px;
    
    .vw-feature-card {
      background: white;
      @include vw-responsive(padding, 16px);
      border-radius: 12px;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
      text-align: center;
      cursor: pointer;
      transition: transform 0.3s ease;
      
      &:hover {
        transform: translateY(-2px);
      }
      
      .vw-feature-icon {
        @include vw-responsive(width, 48px);
        @include vw-responsive(height, 48px);
        @include vw-responsive(border-radius, 12px);
        @include vw-responsive(margin, 0 auto 12px);
        @include vw-responsive(font-size, 24px);
        display: flex;
        align-items: center;
        justify-content: center;
      }
      
      h4 {
        @include vw-responsive(font-size, 16px);
        @include vw-responsive(margin-bottom, 8px);
        font-weight: 600;
        color: #333;
      }
      
      p {
        @include vw-responsive(font-size, 12px);
        color: #666;
        margin: 0;
        line-height: 1.4;
      }
    }
  }
}

.vw-stats-section {
  background: white;
  @include vw-responsive(padding, 20px 16px);
  @include vw-responsive(margin-top, 12px);
  
  .vw-section-title {
    @include vw-responsive(font-size, 20px);
    @include vw-responsive(margin-bottom, 16px);
    font-weight: 600;
    color: #333;
  }
  
  .vw-stats-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 12px;
    
    .vw-stat-item {
      text-align: center;
      @include vw-responsive(padding, 16px);
      background: #f8f9fa;
      border-radius: 8px;
      
      .vw-stat-value {
        @include vw-responsive(font-size, 24px);
        font-weight: 700;
        color: #667eea;
        @include vw-responsive(margin-bottom, 4px);
      }
      
      .vw-stat-label {
        @include vw-responsive(font-size, 12px);
        color: #666;
      }
    }
  }
}

.vw-footer {
  background: white;
  border-top: 1px solid #e5e5e5;
  
  .vw-bottom-nav {
    display: flex;
    justify-content: space-around;
    @include vw-responsive(padding, 8px 0);
    
    .vw-nav-item {
      display: flex;
      flex-direction: column;
      align-items: center;
      cursor: pointer;
      @include vw-responsive(padding, 8px);
      border-radius: 8px;
      transition: all 0.3s ease;
      
      &.active {
        color: #667eea;
        
        .vw-nav-icon {
          background: #667eea;
          color: white;
        }
      }
      
      .vw-nav-icon {
        @include vw-responsive(width, 24px);
        @include vw-responsive(height, 24px);
        @include vw-responsive(border-radius, 6px);
        display: flex;
        align-items: center;
        justify-content: center;
        @include vw-responsive(margin-bottom, 4px);
        @include vw-responsive(font-size, 12px);
        transition: all 0.3s ease;
      }
      
      .vw-nav-text {
        @include vw-responsive(font-size, 10px);
      }
    }
  }
}

// 动画效果
.vw-menu-slide-enter-active,
.vw-menu-slide-leave-active {
  transition: all 0.3s ease;
}

.vw-menu-slide-enter-from,
.vw-menu-slide-leave-to {
  opacity: 0;
  transform: translateY(-10px);
}

// 响应式调整
@media (max-width: 320px) {
  .vw-grid {
    grid-template-columns: 1fr !important;
  }
  
  .vw-stats-grid {
    grid-template-columns: 1fr !important;
  }
}

@media (min-width: 768px) {
  .vw-grid {
    grid-template-columns: repeat(4, 1fr) !important;
  }
  
  .vw-stats-grid {
    grid-template-columns: repeat(4, 1fr) !important;
  }
}

// 横屏适配
@media (orientation: landscape) and (max-height: 500px) {
  .vw-carousel {
    height: 150px !important;
  }
  
  .vw-feature-grid,
  .vw-stats-section {
    padding: 12px 16px !important;
  }
}

// 暗色模式支持
@media (prefers-color-scheme: dark) {
  .vw-demo-container {
    background: #1a1a1a;
  }
  
  .vw-feature-card,
  .vw-stats-section {
    background: #2d2d2d;
    
    h4, .vw-section-title {
      color: #fff;
    }
    
    p {
      color: #ccc;
    }
  }
  
  .vw-footer {
    background: #2d2d2d;
    border-top-color: #404040;
  }
}

// VW响应式混合器
@mixin vw-responsive($property, $values...) {
  @each $value in $values {
    @if type-of($value) == number {
      #{$property}: ($value / 3.75) * 1vw;
    } @else {
      #{$property}: $value;
    }
  }
}
</style>

4.3 Flexible.js完整示例

4.3.1 Flexible.js增强配置

// src/utils/enhanced-flexible.js
(function(win, lib) {
  const doc = win.document;
  const docEl = doc.documentElement;
  let metaEl = doc.querySelector('meta[name="viewport"]');
  const flexibleEl = doc.querySelector('meta[name="flexible"]');
  let dpr = 0;
  let scale = 0;
  let tid;
  const flexible = lib.flexible || (lib.flexible = {});

  // 设备像素比检测
  if (!dpr && !scale) {
    const isAndroid = win.navigator.appVersion.match(/android/gi);
    const isIPhone = win.navigator.appVersion.match(/iphone/gi);
    const devicePixelRatio = win.devicePixelRatio;
    
    if (isIPhone) {
      // iOS设备
      if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
        dpr = 3;
      } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)) {
        dpr = 2;
      } else {
        dpr = 1;
      }
    } else {
      // 其他设备
      dpr = 1;
    }
    scale = 1 / dpr;
  }

  // 设置viewport
  if (!metaEl) {
    metaEl = doc.createElement('meta');
    metaEl.setAttribute('name', 'viewport');
    metaEl.setAttribute('content', 
      `initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}, user-scalable=no`);
    if (docEl.firstElementChild) {
      docEl.firstElementChild.appendChild(metaEl);
    } else {
      const wrap = doc.createElement('div');
      wrap.appendChild(metaEl);
      doc.write(wrap.innerHTML);
    }
  }

  // 设置data-dpr属性
  function setDpr() {
    docEl.setAttribute('data-dpr', dpr);
  }

  // 设置rem基准
  function refreshRem() {
    const width = docEl.getBoundingClientRect().width;
    
    // 限制最大宽度
    const maxWidth = flexible.maxWidth || 540;
    const clientWidth = Math.min(width, maxWidth);
    
    // 计算rem(分成10份)
    const rem = clientWidth / 10;
    docEl.style.fontSize = rem + 'px';
    
    // 触发rem变化事件
    win.dispatchEvent(new CustomEvent('remChange', { 
      detail: { rem: rem, dpr: dpr } 
    }));
  }

  // 事件绑定
  win.addEventListener('resize', function() {
    clearTimeout(tid);
    tid = setTimeout(refreshRem, 300);
  });

  win.addEventListener('pageshow', function(e) {
    if (e.persisted) {
      clearTimeout(tid);
      tid = setTimeout(refreshRem, 300);
    }
  });

  // 横竖屏切换处理
  win.addEventListener('orientationchange', function() {
    clearTimeout(tid);
    tid = setTimeout(refreshRem, 300);
  });

  // 初始化
  if (doc.readyState === 'complete') {
    doc.body.style.fontSize = 12 * dpr + 'px';
    setDpr();
    refreshRem();
  } else {
    doc.addEventListener('DOMContentLoaded', function() {
      doc.body.style.fontSize = 12 * dpr + 'px';
      setDpr();
      refreshRem();
    });
  }

  // 暴露API
  flexible.dpr = dpr;
  flexible.refreshRem = refreshRem;
  flexible.rem2px = function(d) {
    let val = parseFloat(d) * this.rem;
    if (typeof d === 'string' && d.match(/rem$/)) {
      val = parseFloat(d) * this.rem;
    }
    return val;
  };
  flexible.px2rem = function(d) {
    let val = parseFloat(d) / this.rem;
    if (typeof d === 'string' && d.match(/px$/)) {
      val = parseFloat(d) / this.rem;
    }
    return val;
  };

})(window, window['lib'] || (window['lib'] = {}));

// Vue插件封装
export const FlexiblePlugin = {
  install(Vue, options = {}) {
    const { designWidth = 750, maxWidth = 540 } = options;
    
    // 设置配置
    if (window.lib && window.lib.flexible) {
      window.lib.flexible.designWidth = designWidth;
      window.lib.flexible.maxWidth = maxWidth;
    }
    
    // 提供全局方法
    Vue.prototype.$flexible = {
      px2rem(px) {
        return window.lib.flexible.px2rem(px + 'px');
      },
      rem2px(rem) {
        return window.lib.flexible.rem2px(rem + 'rem');
      },
      getRem() {
        return parseFloat(getComputedStyle(document.documentElement).fontSize);
      },
      getDpr() {
        return window.lib.flexible.dpr;
      }
    };
    
    // 自定义指令:rem适配
    Vue.directive('rem', {
      bind(el, binding) {
        const value = binding.value;
        if (typeof value === 'object') {
          Object.keys(value).forEach(prop => {
            if (typeof value[prop] === 'number') {
              el.style[prop] = Vue.prototype.$flexible.px2rem(value[prop]) + 'rem';
            }
          });
        }
      }
    });
  }
};

4.3.2 Flexible.js Vue组件示例

<!-- src/views/FlexibleDemo.vue -->
<template>
  <div class="flexible-demo-container" :data-dpr="dpr">
    <!-- 设备信息展示 -->
    <div class="device-info">
      <div class="info-item">
        <span class="info-label">设备像素比:</span>
        <span class="info-value">{{ dpr }}</span>
      </div>
      <div class="info-item">
        <span class="info-label">当前REM基准:</span>
        <span class="info-value">{{ currentRem }}px</span>
      </div>
      <div class="info-item">
        <span class="info-label">屏幕宽度:</span>
        <span class="info-value">{{ screenWidth }}px</span>
      </div>
    </div>
    
    <!-- 适配演示区域 -->
    <div class="demo-section">
      <h2 class="section-title">Flexible.js 适配演示</h2>
      
      <!-- 1px边框演示 -->
      <div class="border-demo">
        <h3>1px边框解决方案</h3>
        <div class="border-items">
          <div class="border-item hairline">普通边框</div>
          <div class="border-item hairline-top">上边框</div>
          <div class="border-item hairline-bottom">下边框</div>
          <div class="border-item hairline-left">左边框</div>
          <div class="border-item hairline-right">右边框</div>
        </div>
      </div>
      
      <!-- 字体大小适配 -->
      <div class="font-demo">
        <h3>多DPR字体适配</h3>
        <div class="font-items">
          <div v-for="size in fontSizes" 
               :key="size" 
               class="font-item"
               :style="{ fontSize: size + 'px' }">
            {{ size }}px字体 - DPR: {{ dpr }}
          </div>
        </div>
      </div>
      
      <!-- 布局适配 -->
      <div class="layout-demo">
        <h3>弹性布局适配</h3>
        <div class="layout-grid">
          <div v-for="n in 6" 
               :key="n" 
               class="grid-item"
               :style="getGridStyle(n)">
            项目 {{ n }}
          </div>
        </div>
      </div>
      
      <!-- 图片适配 -->
      <div class="image-demo">
        <h3>多倍图适配</h3>
        <div class="image-container">
          <img v-for="img in images" 
               :key="img.id"
               :src="getImageSrc(img)"
               :alt="img.alt"
               class="adaptive-image"
               :style="getImageStyle(img)">
        </div>
      </div>
    </div>
    
    <!-- 交互测试 -->
    <div class="interaction-test">
      <h3>交互元素测试</h3>
      <div class="interaction-items">
        <button class="flexible-button primary" @click="handleButtonClick">
          主要按钮
        </button>
        <button class="flexible-button secondary" @click="handleButtonClick">
          次要按钮
        </button>
        <button class="flexible-button ghost" @click="handleButtonClick">
          幽灵按钮
        </button>
      </div>
      
      <div class="input-group">
        <input v-model="inputValue" 
               class="flexible-input" 
               placeholder="测试输入框适配">
        <button class="flexible-button" @click="handleSubmit">提交</button>
      </div>
    </div>
  </div>
</template>

<script>
import { FlexiblePlugin } from '@/utils/enhanced-flexible';

export default {
  name: 'FlexibleDemo',
  
  // 注册Flexible插件
  beforeCreate() {
    if (!this.$flexible) {
      Vue.use(FlexiblePlugin, {
        designWidth: 750,
        maxWidth: 540
      });
    }
  },
  
  data() {
    return {
      dpr: 1,
      currentRem: 0,
      screenWidth: 0,
      inputValue: '',
      fontSizes: [12, 14, 16, 18, 20, 24],
      images: [
        { id: 1, width: 200, height: 150, alt: '示例图片1' },
        { id: 2, width: 180, height: 120, alt: '示例图片2' },
        { id: 3, width: 220, height: 160, alt: '示例图片3' }
      ]
    };
  },
  
  mounted() {
    this.initFlexible();
    this.bindEvents();
  },
  
  beforeDestroy() {
    this.unbindEvents();
  },
  
  methods: {
    initFlexible() {
      // 获取当前设备信息
      this.dpr = this.$flexible.getDpr();
      this.currentRem = this.$flexible.getRem();
      this.screenWidth = window.screen.width;
      
      console.log('Flexible.js 初始化完成:', {
        dpr: this.dpr,
        rem: this.currentRem,
        screenWidth: this.screenWidth
      });
    },
    
    bindEvents() {
      // 监听rem变化
      window.addEventListener('remChange', this.handleRemChange);
      window.addEventListener('resize', this.handleResize);
    },
    
    unbindEvents() {
      window.removeEventListener('remChange', this.handleRemChange);
      window.removeEventListener('resize', this.handleResize);
    },
    
    handleRemChange(event) {
      this.currentRem = event.detail.rem;
      this.dpr = event.detail.dpr;
    },
    
    handleResize() {
      this.screenWidth = window.innerWidth;
    },
    
    getGridStyle(index) {
      const colors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57', '#ff9ff3'];
      return {
        backgroundColor: colors[(index - 1) % colors.length],
        height: this.$flexible.px2rem(80) + 'rem'
      };
    },
    
    getImageSrc(img) {
      // 根据DPR加载不同倍数的图片
      const multiplier = this.dpr >= 3 ? 3 : this.dpr >= 2 ? 2 : 1;
      return `https://picsum.photos/${img.width * multiplier}/${img.height * multiplier}?random=${img.id}`;
    },
    
    getImageStyle(img) {
      return {
        width: this.$flexible.px2rem(img.width) + 'rem',
        height: this.$flexible.px2rem(img.height) + 'rem'
      };
    },
    
    handleButtonClick() {
      this.$message.success('按钮点击成功!当前DPR: ' + this.dpr);
    },
    
    handleSubmit() {
      if (this.inputValue) {
        this.$message.info(`输入内容: ${this.inputValue}`);
        this.inputValue = '';
      }
    }
  }
};
</script>

<style lang="scss" scoped>
// 引入Flexible.js适配样式
@import '@/assets/styles/adaption/flexible.scss';

.flexible-demo-container {
  min-height: 100vh;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  padding: 0.4rem; // 20px in 750 design
}

.device-info {
  background: rgba(255, 255, 255, 0.95);
  border-radius: 0.2rem; // 10px
  padding: 0.3rem; // 15px
  margin-bottom: 0.4rem; // 20px
  display: flex;
  justify-content: space-around;
  flex-wrap: wrap;
  
  .info-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin: 0.1rem; // 5px
    
    .info-label {
      font-size: 0.24rem; // 12px
      color: #666;
      margin-bottom: 0.1rem; // 5px
    }
    
    .info-value {
      font-size: 0.32rem; // 16px
      font-weight: 600;
      color: #333;
    }
  }
}

.demo-section {
  background: white;
  border-radius: 0.2rem; // 10px
  padding: 0.3rem; // 15px
  margin-bottom: 0.4rem; // 20px
  
  .section-title {
    font-size: 0.36rem; // 18px
    font-weight: 600;
    color: #333;
    text-align: center;
    margin-bottom: 0.3rem; // 15px
  }
}

.border-demo {
  margin-bottom: 0.4rem; // 20px
  
  h3 {
    font-size: 0.28rem; // 14px
    color: #666;
    margin-bottom: 0.2rem; // 10px
  }
  
  .border-items {
    display: flex;
    flex-wrap: wrap;
    gap: 0.2rem; // 10px
    
    .border-item {
      padding: 0.2rem; // 10px
      text-align: center;
      border-radius: 0.1rem; // 5px
      font-size: 0.24rem; // 12px
      
      // 1px边框解决方案
      &.hairline {
        border: 1px solid #e5e5e5;
      }
      
      &.hairline-top {
        border-top: 1px solid #e5e5e5;
      }
      
      &.hairline-bottom {
        border-bottom: 1px solid #e5e5e5;
      }
      
      &.hairline-left {
        border-left: 1px solid #e5e5e5;
      }
      
      &.hairline-right {
        border-right: 1px solid #e5e5e5;
      }
    }
  }
}

.font-demo {
  margin-bottom: 0.4rem; // 20px
  
  h3 {
    font-size: 0.28rem; // 14px
    color: #666;
    margin-bottom: 0.2rem; // 10px
  }
  
  .font-items {
    display: flex;
    flex-direction: column;
    gap: 0.2rem; // 10px
    
    .font-item {
      padding: 0.2rem; // 10px
      background: #f8f9fa;
      border-radius: 0.1rem; // 5px
      text-align: center;
    }
  }
}

.layout-demo {
  margin-bottom: 0.4rem; // 20px
  
  h3 {
    font-size: 0.28rem; // 14px
    color: #666;
    margin-bottom: 0.2rem; // 10px
  }
  
  .layout-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.2rem; // 10px
    
    .grid-item {
      padding: 0.3rem; // 15px
      color: white;
      text-align: center;
      border-radius: 0.1rem; // 5px
      font-size: 0.24rem; // 12px
      display: flex;
      align-items: center;
      justify-content: center;
    }
  }
}

.image-demo {
  margin-bottom: 0.4rem; // 20px
  
  h3 {
    font-size: 0.28rem; // 14px
    color: #666;
    margin-bottom: 0.2rem; // 10px
  }
  
  .image-container {
    display: flex;
    gap: 0.2rem; // 10px
    overflow-x: auto;
    
    .adaptive-image {
      border-radius: 0.1rem; // 5px
      object-fit: cover;
      flex-shrink: 0;
    }
  }
}

.interaction-test {
  background: white;
  border-radius: 0.2rem; // 10px
  padding: 0.3rem; // 15px
  
  h3 {
    font-size: 0.28rem; // 14px
    color: #666;
    margin-bottom: 0.2rem; // 10px
  }
  
  .interaction-items {
    display: flex;
    gap: 0.2rem; // 10px
    margin-bottom: 0.3rem; // 15px
    flex-wrap: wrap;
    
    .flexible-button {
      padding: 0.2rem 0.4rem; // 10px 20px
      border: none;
      border-radius: 0.1rem; // 5px
      font-size: 0.28rem; // 14px
      cursor: pointer;
      transition: all 0.3s ease;
      
      &.primary {
        background: #667eea;
        color: white;
        
        &:hover {
          background: #5a6fd8;
        }
      }
      
      &.secondary {
        background: #e9ecef;
        color: #333;
        
        &:hover {
          background: #dee2e6;
        }
      }
      
      &.ghost {
        background: transparent;
        color: #667eea;
        border: 1px solid #667eea;
        
        &:hover {
          background: #667eea;
          color: white;
        }
      }
    }
  }
  
  .input-group {
    display: flex;
    gap: 0.2rem; // 10px
    
    .flexible-input {
      flex: 1;
      padding: 0.2rem 0.3rem; // 10px 15px
      border: 1px solid #e5e5e5;
      border-radius: 0.1rem; // 5px
      font-size: 0.28rem; // 14px
      
      &:focus {
        outline: none;
        border-color: #667eea;
      }
    }
  }
}

// DPR特定样式
[data-dpr="1"] {
  .font-item {
    font-weight: normal;
  }
  
  .flexible-button {
    font-weight: normal;
  }
}

[data-dpr="2"] {
  .font-item {
    font-weight: 500;
  }
  
  .flexible-button {
    font-weight: 500;
  }
  
  .hairline {
    border-width: 0.5px;
  }
}

[data-dpr="3"] {
  .font-item {
    font-weight: 600;
  }
  
  .flexible-button {
    font-weight: 600;
  }
  
  .hairline {
    border-width: 0.333px;
  }
}

// 响应式调整
@media (max-width: 768px) {
  .layout-grid {
    grid-template-columns: repeat(2, 1fr) !important;
  }
  
  .interaction-items {
    flex-direction: column;
    
    .flexible-button {
      width: 100%;
    }
  }
}

@media (max-width: 480px) {
  .layout-grid {
    grid-template-columns: 1fr !important;
  }
  
  .device-info {
    flex-direction: column;
    align-items: center;
    
    .info-item {
      margin: 0.1rem 0;
    }
  }
}

// 横屏适配
@media (orientation: landscape) and (max-height: 500px) {
  .flexible-demo-container {
    padding: 0.2rem;
  }
  
  .demo-section {
    padding: 0.2rem;
  }
}

// 暗色模式支持
@media (prefers-color-scheme: dark) {
  .demo-section,
  .interaction-test {
    background
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。