2023-09-25
Angular
00
请注意,本文编写于 810 天前,最后修改于 642 天前,其中某些信息可能已经过时。

在开发数据大屏时最头疼的就是多分辨率适配问题,可行方案有rem方式、媒体查询、flex+百分比+vh、scale方式。这里采用scale方式,因为使用其它方式时,在使用echarts时,一些字体大小的配置参数无法转换大小,就会造成在低分辨下,字体太大不美观。使用scale方式开发时,我们在编写css样式时,就可以根据UI提供的设计稿直接写具体的数值,比如

css
.header{ height: 110px; width: 100%; padding: 0 10px 4px 10px; }

编写通用组件,自动将大屏页面进行scale放大缩小

screen-adpter.component.html

html
<div class="tl-screen-adpter-container" [class.fit]="mode == 'fit'" [class.scrollY] = "mode == 'scrollY'" [class.scrollX] = "mode == 'scrollX'" [class.full] = "mode == 'full'"> <div class="tl-screen-adpter-scale" #datav *ngIf="!showEntity"> <ng-container *ngTemplateOutlet="inner"></ng-container> </div> <div #entity *ngIf="showEntity" class="tl-screen-adpter-entity"> <div class="tl-screen-adpter-scale" #datav> <ng-container *ngTemplateOutlet="inner"></ng-container> </div> </div> <ng-template #inner> <div [style]="canvasStyle"> <ng-content></ng-content> </div> </ng-template> </div>

screen-adpter.component.ts

ts
import { AfterViewInit, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild } from "@angular/core"; import { usePreviewFitScale, usePreviewFullScale, usePreviewScrollXScale, usePreviewScrollYScale } from "./previewScale"; type modeType = "fit" | "scrollY" | "scrollX" | "full" @Component({ selector: 'tl-screen-adpter', templateUrl: './screen-adpter.component.html', styleUrls: ['./screen-adpter.component.less'], host: { class: "tl-screen-adpter" } }) export class TlScreenAdapterComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges { canvasStyle = {} // * 默认缩放值 scale = { width: 1, height: 1, } scaleRef = { width: 1, height: 1, } showEntity = false @Input() mode: modeType = "fit" @Input() canvasConfig = { width: 1920, height: 1080, } @ViewChild("datav", { static: false }) previewRef: any @ViewChild("entity", { static: false }) entityRef: any unWindowResize: Function ngOnChanges(changes: SimpleChanges): void { if (changes.mode) { const type = changes.mode.currentValue this.showEntity = type === "scrollX" || type === "scrollY" } } ngOnInit(): void { } ngAfterViewInit(): void { this.initCanvas() switch (this.mode) { case "fit": (() => { const { calcRate, windowResize, unWindowResize } = usePreviewFitScale( this.canvasConfig.width, this.canvasConfig.height, this.previewRef.nativeElement, this.updateScaleRef ) calcRate() windowResize() this.unWindowResize = unWindowResize })() break case "scrollY": (() => { const { calcRate, windowResize, unWindowResize } = usePreviewScrollYScale( this.canvasConfig.width, this.canvasConfig.height, this.previewRef.nativeElement, scale => { const dom = this.entityRef.nativeElement dom.style.width = `${this.canvasConfig.width * scale.width}px` dom.style.height = `${this.canvasConfig.height * scale.height}px` this.updateScaleRef(scale) } ) calcRate() windowResize() this.unWindowResize = unWindowResize })() break case "scrollX": (() => { const { calcRate, windowResize, unWindowResize } = usePreviewScrollXScale( this.canvasConfig.width, this.canvasConfig.height, this.previewRef.nativeElement, scale => { const dom = this.entityRef.nativeElement dom.style.width = `${this.canvasConfig.width * scale.width}px` dom.style.height = `${this.canvasConfig.height * scale.height}px` this.updateScaleRef(scale) } ) calcRate() windowResize() this.unWindowResize = unWindowResize })() break case "full": (() => { const { calcRate, windowResize, unWindowResize } = usePreviewFullScale( this.canvasConfig.width, this.canvasConfig.height, this.previewRef.nativeElement, this.updateScaleRef ) calcRate() windowResize() this.unWindowResize = unWindowResize })() break } } ngOnDestroy(): void { //window.removeEventListener('resize', this.onResize) if (this.unWindowResize) { this.unWindowResize() } } /** 初始化画布容器大小 */ initCanvas() { this.canvasStyle = { position: 'relative' as const, width: this.canvasConfig.width ? `${this.canvasConfig.width || 100}px` : '100%', height: this.canvasConfig.height ? `${this.canvasConfig.height}px` : '100%', } } updateScaleRef = (scale: { width: number; height: number }) => { // 这里需要解构,保证赋值给scaleRef的为一个新对象 // 因为scale始终为同一引用 this.scaleRef = { ...scale } } }

previewScale.ts 封装了适配方案,提供如下几种方案

  • fit 保持宽高比放大和缩小,两边可能会留白
  • scrollX 高度充满,宽度自使用滚动
  • scrollY 宽度充满,高度自使用滚动
  • full 强行拉伸充满屏幕,可能造成字体压缩
ts
import { throttle } from "lodash-es" // * 屏幕缩放适配(两边留白) export const usePreviewFitScale = ( width: number, height: number, scaleDom: HTMLElement | null, callback?: (scale: { width: number; height: number; }) => void ) => { // * 画布尺寸(px) const baseWidth = width const baseHeight = height // * 默认缩放值 const scale = { width: 1, height: 1, } // * 需保持的比例 const baseProportion = parseFloat((baseWidth / baseHeight).toFixed(5)) const calcRate = () => { // 当前屏幕宽高比 const currentRate = parseFloat( (window.innerWidth / window.innerHeight).toFixed(5) ) if (scaleDom) { if (currentRate > baseProportion) { // 表示更宽 scale.width = parseFloat(((window.innerHeight * baseProportion) / baseWidth).toFixed(5)) scale.height = parseFloat((window.innerHeight / baseHeight).toFixed(5)) scaleDom.style.transform = `scale(${scale.width}, ${scale.height})` } else { // 表示更高 scale.height = parseFloat(((window.innerWidth / baseProportion) / baseHeight).toFixed(5)) scale.width = parseFloat((window.innerWidth / baseWidth).toFixed(5)) scaleDom.style.transform = `scale(${scale.width}, ${scale.height})` } if (callback) callback(scale) } } const resize = throttle(() => { calcRate() }, 200) // * 改变窗口大小重新绘制 const windowResize = () => { window.addEventListener('resize', resize) } // * 卸载监听 const unWindowResize = () => { console.log("卸载监听"); window.removeEventListener('resize', resize) } return { calcRate, windowResize, unWindowResize, } } // * X轴撑满,Y轴滚动条 export const usePreviewScrollYScale = ( width: number, height: number, scaleDom: HTMLElement | null, callback?: (scale: { width: number; height: number; }) => void ) => { // * 画布尺寸(px) const baseWidth = width const baseHeight = height // * 默认缩放值 const scale = { width: 1, height: 1, } // * 需保持的比例 const baseProportion = parseFloat((baseWidth / baseHeight).toFixed(5)) const calcRate = () => { if (scaleDom) { scale.height = parseFloat(((window.innerWidth / baseProportion) / baseHeight).toFixed(5)) scale.width = parseFloat((window.innerWidth / baseWidth).toFixed(5)) scaleDom.style.transform = `scale(${scale.width}, ${scale.height})` if (callback) callback(scale) } } const resize = throttle(() => { calcRate() }, 200) // * 改变窗口大小重新绘制 const windowResize = () => { window.addEventListener('resize', resize) } // * 卸载监听 const unWindowResize = () => { window.removeEventListener('resize', resize) } return { calcRate, windowResize, unWindowResize, } } // * Y轴撑满,X轴滚动条 export const usePreviewScrollXScale = ( width: number, height: number, scaleDom: HTMLElement | null, callback?: (scale: { width: number; height: number; }) => void ) => { // * 画布尺寸(px) const baseWidth = width const baseHeight = height // * 默认缩放值 const scale = { height: 1, width: 1, } // * 需保持的比例 const baseProportion = parseFloat((baseWidth / baseHeight).toFixed(5)) const calcRate = () => { if (scaleDom) { scale.width = parseFloat(((window.innerHeight * baseProportion) / baseWidth).toFixed(5)) scale.height = parseFloat((window.innerHeight / baseHeight).toFixed(5)) scaleDom.style.transform = `scale(${scale.width}, ${scale.height})` if (callback) callback(scale) } } const resize = throttle(() => { calcRate() }, 200) // * 改变窗口大小重新绘制 const windowResize = () => { window.addEventListener('resize', resize) } // * 卸载监听 const unWindowResize = () => { window.removeEventListener('resize', resize) } return { calcRate, windowResize, unWindowResize, } } // * 变形内容,宽高铺满 export const usePreviewFullScale = ( width: number, height: number, scaleDom: HTMLElement | null, callback?: (scale: { width: number; height: number; }) => void ) => { // * 默认缩放值 const scale = { width: 1, height: 1, } const calcRate = () => { if (scaleDom) { scale.width = parseFloat((window.innerWidth / width).toFixed(5)) scale.height = parseFloat((window.innerHeight / height).toFixed(5)) scaleDom.style.transform = `scale(${scale.width}, ${scale.height})` if (callback) callback(scale) } } const resize = throttle(() => { calcRate() }, 200) // * 改变窗口大小重新绘制 const windowResize = () => { window.addEventListener('resize', resize) } // * 卸载监听 const unWindowResize = () => { window.removeEventListener('resize', resize) } return { calcRate, windowResize, unWindowResize, } }

screen-adpter.component.less

less
.tl-screen-adpter-container{ background-color: #152047; position: relative; height: 100vh; width: 100vw; &.fit{ display: flex; align-items: center; justify-content: center; overflow: hidden; .tl-screen-adpter-scale{ transform-origin: center center; } } &.scrollY{ overflow-x: hidden; .tl-screen-adpter-scale{ transform-origin: left top; } } &.scrollX{ overflow-y: hidden; .tl-screen-adpter-scale{ transform-origin: left top; } } &.full{ display: flex; align-items: center; justify-content: center; overflow: hidden; .tl-screen-adpter-scale{ transform-origin: center center; } } &.tl-screen-adpter-entity{ overflow: hidden; } }

在大屏使用组件使用组件

html
<div class="screen preview"> <tl-screen-adpter mode="scrollY"> <div class="screen__container"> <div class="header-box"> <div class="header"> <div class="logo"> <img src="assets/images/logo.png" alt=""> <span>XXXX管理平台</span> </div> <div class="tile_level1"><span>大屏标题</span></div> </div> </div> </div> </tl-screen-adpter> </div>
如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:千寻

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!