乐于分享
好东西不私藏

大屏自适应组件技术文档

大屏自适应组件技术文档

概述

本方案为前端大屏项目提供统一的屏幕自适应能力,通过一个包装组件实现以下核心功能:

  • 自动旋转:当屏幕高度大于宽度(竖屏设备)时,强制将内容旋转90°,保持横屏布局。
  • 等比例缩放:以设计稿宽度为基准,确保画面宽度完全展示,高度不足时居中裁切(亦可调整为高度适配)。
  • 响应式监听:窗口尺寸变化时实时更新旋转状态和缩放比例。
  • 框架通用:提供 Vue 3、React 18、Angular 15+ 三大主流框架的封装实现,任何页面、插件组件(如 ECharts、DataV)只需包裹即可生效。

核心原理

1. 旋转逻辑

  • 判定条件:window.innerHeight > window.innerWidth
  • 旋转策略:当判定为竖屏时,将内容容器旋转 90° 并平移归位,使之填满视口。
    容器尺寸互换:宽度 = 原屏幕高度 (100vh),高度 = 原屏幕宽度 (100vw)。
  • 关键 CSS(当旋转时):
    width: 100vh;
    height: 100vw;
    transform: rotate(90deg) translateY(-100%);
    transform-origin: top left;
    旋转后原点仍在左上角,需上移自身高度(即 100% 的原容器高度 = 100vw)才能对齐视口。

2. 缩放适配

  • 设计稿尺寸:通常为 1920×1080(可按项目配置)。
  • 缩放比例:scale = 当前容器宽度 / 设计稿宽度 由于旋转后容器宽度等于实际可见宽度,直接以此为基准即可实现“长边完全展示”。
  • 缩放层定位:内部缩放层保持设计稿原尺寸,通过 transform: scale() 缩放并居中显示,外层 overflow: hidden 隐藏溢出。

3. 响应式处理

  • 监听 window.resize 事件(可增加防抖优化)。
  • 或使用 ResizeObserver 监听父容器(非全屏场景)。

组件实现

一、Vue 3 版本(Composition API + 插槽)

<!-- AdaptScreen.vue -->
<template>
    <div class="adapt-outer">
        <div class="adapt-inner" :class="{ rotated: isRotated }">
            <div class="scale-layer" :style="scaleStyle">
                <slot />
            </div>
        </div>
    </div>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'

const props = defineProps({
    designWidth: { type: Number, default: 1920 },
    designHeight: { type: Number, default: 1080 },
})

const isRotated = ref(false)
const scale = ref(1)

function update() {
    const w = window.innerWidth
    const h = window.innerHeight
    isRotated.value = h > w

    const containerW = isRotated.value ? h : w
    scale.value = containerW / props.designWidth
}

const scaleStyle = computed(() => ({
    width: `${props.designWidth}px`,
    height: `${props.designHeight}px`,
    transform: `scale(${scale.value})`,
}))

onMounted(() => {
    update()
    window.addEventListener('resize', update)
})
onUnmounted(() => window.removeEventListener('resize', update))
</script>

<style scoped>
.adapt-outer {
    width: 100vw;
    height: 100vh;
    overflow: hidden;
    background: #000; /* 可按需修改 */
}
.adapt-inner {
    width: 100%;
    height: 100%;
}
.rotated {
    width: 100vh;
    height: 100vw;
    transform: rotate(90deg) translateY(-100%);
    transform-origin: top left;
}
.scale-layer {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}
</style>

使用示例

<template>
    <AdaptScreen :designWidth="1920" :designHeight="1080">
        <YourDashboardContent />
    </AdaptScreen>
</template>

<script setup>
    import AdaptScreen from './components/AdaptScreen.vue'
</script>

二、React 版本(Hooks + JSX)

// AdaptScreen.jsx
import { useState, useEffect } from 'react'

function useAdaptScreen(designWidth = 1920, designHeight = 1080) {
    const [size, setSize] = useState({ w: window.innerWidth, h: window.innerHeight })

    useEffect(() => {
    const handleResize = () => setSize({ w: window.innerWidth, h: window.innerHeight })
    window.addEventListener('resize', handleResize)
    return () => window.removeEventListener('resize', handleResize)
    }, [])

    const rotated = size.h > size.w
    const containerWidth = rotated ? size.h : size.w
    const scale = containerWidth / designWidth

    const outerStyle = {
        width: '100vw',
        height: '100vh',
        overflow: 'hidden',
        background: '#000',
    }
    const innerStyle = rotated
        ? {
            width: '100vh',
            height: '100vw',
            transform: 'rotate(90deg) translateY(-100%)',
            transformOrigin: 'top left',
        }
        : { width: '100%', height: '100%' }
    const layerStyle = {
        width: designWidth,
        height: designHeight,
        transform: `scale(${scale})`,
        position: 'absolute',
        top: '50%',
        left: '50%',
        marginTop: -designHeight / 2,
        marginLeft: -designWidth / 2,
    }

    return { outerStyle, innerStyle, layerStyle }
}

export default function AdaptScreen({ designWidth = 1920, designHeight = 1080, children }) {
    const { outerStyle, innerStyle, layerStyle } = useAdaptScreen(designWidth, designHeight)
    return (
        <div style={outerStyle}>
            <div style={innerStyle}>
                <div style={layerStyle}>{children}</div>
            </div>
        </div>
    )
}

使用示例

import AdaptScreen from './AdaptScreen'

function App() {
    return (
        <AdaptScreen designWidth={1920} designHeight={1080}>
            <Dashboard />
        </AdaptScreen>
    )
}

三、Angular 版本(Standalone Component)

// adapt-screen.component.ts
import { Component, Input, OnInit, OnDestroy } from '@angular/core';

@Component({
    selector: 'app-adapt-screen',
    standalone: true,
    template: `
        <div class="adapt-outer">
            <div class="adapt-inner" [class.rotated]="isRotated">
                <div
                class="scale-layer"
                [style.width.px]="designWidth"
                [style.height.px]="designHeight"
           [style.transform]="'scale(' + scale + ')'"
           >
                    <ng-content></ng-content>
                </div>
            </div>
        </div>
    `,
    styles: [`
        .adapt-outer {
            width: 100vw;
            height: 100vh;
            overflow: hidden;
            background: #000;
        }
        .adapt-inner {
            width: 100%;
            height: 100%;
        }
        .rotated {
            width: 100vh;
            height: 100vw;
            transform: rotate(90deg) translateY(-100%);
            transform-origin: top left;
        }
        .scale-layer {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
        }
    `]
})
export class AdaptScreenComponent implements OnInit, OnDestroy {
    @Input() designWidth = 1920;
    @Input() designHeight = 1080;

    isRotated = false;
    scale = 1;

    private resizeHandler = () => this.update();

    ngOnInit(): void {
        this.update();
        window.addEventListener('resize', this.resizeHandler);
    }

    ngOnDestroy(): void {
        window.removeEventListener('resize', this.resizeHandler);
    }

    private update(): void {
        const w = window.innerWidth;
        const h = window.innerHeight;
        this.isRotated = h > w;

        const containerW = this.isRotated ? h : w;
        this.scale = containerW / this.designWidth;
    }
}

使用示例(在任意组件模板中):

<app-adapt-screen [designWidth]="1920" [designHeight]="1080">
    <app-dashboard></app-dashboard>
</app-adapt-screen>

提示:若使用传统 NgModule 方式,请移除 standalone: true 并将组件在模块中声明。

插件与第三方组件集成

所有第三方可视化库(如 ECharts、DataV、AntV 等)均可直接作为子内容插入,只需确保其布局基于设计稿尺寸(如 1920×1080)进行绝对定位或百分比布局。

示例(Vue + DataV)

<AdaptScreen>
    <dv-full-screen-container>
        <dv-border-box>内容</dv-border-box>
    </dv-full-screen-container>
</AdaptScreen>

React 示例

<AdaptScreen>
    <FullScreenContainer>
        <BorderBox>内容</BorderBox>
    </FullScreenContainer>
</AdaptScreen>

可选增强配置

  1. 防抖处理
    为 resize 事件增加防抖(如 debounce 100ms),减少高频计算压力。
  2. 高度适配模式
    若希望高度完全展示(宽度裁切),将缩放计算改为:
    scale = containerHeight / designHeight,同时调整旋转容器逻辑。
  3. 安全边距
    可将 designWidth / designHeight 设置得略大于实际内容尺寸,防止边缘被裁切。
  4. 监听父容器而非窗口
    使用 ResizeObserver 替代 window.resize,适用于非全屏的大屏嵌入场景。
  5. CSS 变量支持
    将设计尺寸和背景色等定义为 CSS 变量,便于主题化定制。

注意事项

  • 旋转后的内容会交换宽高,内部元素的鼠标事件坐标可能需要适配(通常无需额外处理,因为缩放层已基于设计稿)。
  • 若页面中存在绝对定位的弹出层(如模态框),需确保其也挂载在缩放层内部或手动应用相同变换。
  • 在极端分辨率下,缩放可能导致字体模糊,建议使用矢量图标和 SVG 资源。
  • 旋转时浏览器可能对部分 CSS 动画有性能影响,建议优先使用 transform 和 opacity 实现动画。

总结

通过将旋转、缩放逻辑抽象为单一组件,所有页面和插件均可零侵入实现大屏自适应。本文档提供的三大框架实现可直接复制使用,降低多分辨率适配成本,保障大屏项目视觉一致性。