一次大屏可视化项目自适应可行性方案探究(zoom缩放方案)

3,517 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

背景

目前所在公司主做大屏可视化项目(智慧楼宇、智慧园区等等方向),之前是要求按照客户的屏幕尺寸写死即可,但是最近做一个公司自己的用于对外宣传的可视化demo,领导要求必须要适配笔记本。既然是demo的话,而且主要以笔记本来进行展示,那么这边就和UI商量,设计稿以1920X1080的尺寸来进行设计,前端按照这个尺寸进行开发

问题

在适配的过程中,遇到最大的难点就是windows系统自带缩放,导致页面不能适配的问题。windows系统自带缩放会影响到浏览器视口的宽高变化,其本质还是要去适配任何分辨率

探究方案

  • rem 适配
  • scale适配
  • zoom 适配

解决历程

rem布局

利用 postcss-pxtorem 插件,在webpack打包的时候,将css中的px自动转成rem。

具体实现

  1. 安装 yarn add postcss postcss-loader postcss-pxtorem -D

  2. 配置

  • 项目根目录下创建postcss.config.js文件
module.exports = {
  plugins: [
    [
      'postcss-preset-env',
      {
        // autoprefixer: {
        //   grid: true,
        // },
      },
    ],
    [
      'postcss-pxtorem',
      {
        rootValue: 100,
        propList: ['*'],
      },
    ],
  ],
};
  • 修改webpack配置,编译sass/less的时候,新增postcss-loader,注意:postcss-loader需要配置在css-loader之前执行
{
  loader: require.resolve('postcss-loader'),
},
  • 编写动态修改html标签fontSize的大小
var htmlDOM = document.getElementsByTagName('html')[0];
var designWidth = 1920 // 设计稿的宽度  --- 已知
var designHtmlFontSize = 100; // 设计稿上面的html font-size的大小  --- 已知
var actualHtmlFontSize; // 实际html font-size的大小 --- 未知
function setRootFontSize() {
  var viewportWidth = document.documentElement.clientWidth || document.body.clientWidth; // 实际浏览器视口的宽度 ---- 已知
  actualHtmlFontSize = viewportWidth * designHtmlFontSize / designWidth; // 3个已知条件,1个未知条件
  htmlDOM.style.fontSize = actualHtmlFontSize + 'px';
}

setRootFontSize()
window.addEventListener("resize", setRootFontSize)

总结

优势: 自适应流畅,实时根据浏览器视口的宽度变化而适应。开发者书写样式的时候,无需考虑单位转换问题。

劣势:会受到浏览器fontSize最小为12px的影响,同时也会受到windows系统自带的缩放影响,根据实际使用来看rem布局在web端只适配不同分辨率但是同一宽高比,并且windows系统缩放为100% 的情况。

所以在web端采用rem布局不太合适,移动端采用rem布局问题是不大的。

scale缩放

利用css 3提供的transform: scale属性进行页面的缩放

具体实现

通过监测window身上的resize事件,在resize事件中,实时的获取浏览器视口的宽和高,用视口的宽、高度与UI设计稿的宽、高度进行比较,得出实际的zoom的值。

useEffect(() => {
 calculateCssScale();
    window.addEventListener('resize', calculateCssScale);
  return () => {
    window.removeEventListener('resize', calculateCssScale);
  };
}, []);

/**
 * 计算CSS scale  -- 保持UI设计稿比例的缩放  页面不会出现压缩情况
 */
const calculateCssScaleKeepRate = () => {
  const uiWidth = 1920;
  const uiHeight = 1080;
  const wrapperDOM: HTMLDivElement = document.querySelector('#ice-container')!;
  wrapperDOM.style.width = `${uiWidth}px`;
  wrapperDOM.style.height = `${uiHeight}px`;
  const viewportHeight: number = document.documentElement.clientHeight || document.body.clientHeight; // 实际浏览器视口的高度
  const viewportWidth: number = document.documentElement.clientWidth || document.body.clientWidth; // 实际浏览器视口的宽度
  const scale =
    viewportWidth / viewportHeight < uiWidth / uiHeight ? viewportWidth / uiWidth : viewportHeight / uiHeight;
  wrapperDOM.style.transform = `scale(${scale})`;
  wrapperDOM.style.transformOrigin = 'left top';
  if (viewportWidth / viewportHeight < uiWidth / uiHeight) {
    const margin = viewportHeight - wrapperDOM.getBoundingClientRect().height;
    wrapperDOM.style.margin = `${margin / 2}px 0`;
  } else {
    const margin = viewportWidth - wrapperDOM.getBoundingClientRect().width;
    wrapperDOM.style.margin = `0 ${margin / 2}px`;
  }
};


/**
 * 计算CSS scale  --- 不保持比例  会压缩页面
 */
const calculateCssScale = () => {
  const uiWidth = 1920;
  const uiHeight = 1080;
  const bodyDOM = document.getElementsByTagName('body')[0];
  const gaodeDOM = document.querySelector('#gaode-map');

  const scaleX = window.innerWidth / uiWidth;
  const scaleY = window.innerHeight / uiHeight;
  // const offsetX = (window.innerWidth - uiWidth) / (2 * scaleX);
  // const offsetY = (window.innerHeight - uiHeight) / (2 * scaleX);
  bodyDOM.style.width = `${uiWidth}px`;
  bodyDOM.style.height = `${uiHeight}px`;
  // bodyDOM.style.transform = `scale(${scaleX},${scaleX}) translate(${offsetX}px,${offsetY}px)`;
  bodyDOM.style.transform = `scale(${scaleX},${scaleY})`;
  bodyDOM.style.transformOrigin = 'left top';
};

总结

优势

  • 兼容性更好
  • 不会引起回流,只会发生重绘

劣势

scale缩放有两种方式,一种是缩放不保持UI设计稿比例缩放,另一种是保持UI设计稿比例缩放

不保持UI设计稿比例缩放

  • 对于同一比例的分辨率适配,对于不同比例的分辨率,scale缩放会直接导致页面有压缩。
  • 对于antd组件库中的Select的弹出层位置也会出现很大偏差
  • 如果地图是高德地图或者其他GIS地图的话,会导致鼠标点击地图的点有位置偏差, 此种情况下,采用对地图再次应用scale,地图的scale值与body身上的scale值相乘等于1即可

保持UI设计稿比例缩放

  • 如果地图是高德地图或者其他GIS地图的话,会导致鼠标点击地图的点有位置偏差。(目前没找到完美解决方案)
  • 对于antd组件库中的Select的弹出层位置也会出现很大偏差

zoom缩放

介绍

利用css3 提供的zoom属性进行页面的缩放。zoom缩放,默认是从元素的左上角进行缩放,被缩放的元素,元素本身的大小会发生改变,此行为会直接引起文档流的回流与重绘。

具体实现

通过监测window身上的resize事件,在resize事件中,实时的获取浏览器视口的宽和高,用视口的宽、高度与UI设计稿的宽、高度进行比较,得出实际的zoom的值。

react 项目案例

const [commonState, commonDispatchers] = store.useModel('commonModel');

useEffect(() => {
  calculateCssZoom();
  window.addEventListener('resize', calculateCssZoom);
  return () => {
    window.removeEventListener('resize', calculateCssZoom);
  };
}, []);

/**
 * 计算CSS zoom
 */
const calculateCssZoom = () => {
  if (domRef.current) {
    const viewportHeight: number = document.documentElement.clientHeight || document.body.clientHeight; // 实际浏览器视口的高度
    const viewportWidth: number = document.documentElement.clientWidth || document.body.clientWidth; // 实际浏览器视口的宽度
    const designHeight: number = 1080; // 设计稿的高度
    const designWidth: number = 1920; // 设计稿的宽度
    const zoomValueHeight = domHeight / designHeight; // 高度计算出来的zoom值
    const zoomValueWidth = domWidth / designWidth; // 宽度计算出来的zoom值
    const targetZoom = zoomValueWidth > zoomValue ? zoomValue : zoomValueWidth; // 比较大小选择最终实际需要的zoom值
    // 拿到最终的zoom值之后,可以选择直接应用到body标签身上,或者将zoom值储存在store里面,分发到需要被适配的组件里面使用,笔者这里采用的是储存store向下分发的方案。(根据实际情况选择)
    commonDispatchers.setState({
      cssZoom: targetZoom,
    });
  }
};

return (
   <div style={{zoom: commonState.cssZoom}} >大屏内容</div>
)

总结

优势

  • 几乎完美适配各种宽高比的分辨率(故解决了windows系统自带的缩放比导致页面适配问题)
  • 没有两边或者上下留白的情况
  • 不受浏览器最小12px的影响

劣势:、

  • 会引起回流和重绘,导致性能要比scale方式差
  • 兼容性差

最终我选择zoom缩放方案

我最终选择的是zoom的方式进行适配。理由如下:

  1. 大屏可视化平台用户量少,基本上属于只在一台电脑上进行显示,所以我们可以直接推荐客户用chorme浏览器去打开网页。以此来解决zoom的兼容性问题。
  2. scale其实是等比例缩放比较好,非等比例会出现上下 或者 左右留白的情况,如果非要完全充满浏览器视口,则会有很明显的页面被压缩的情况。, scale 缩放还有一些问题到现在没找到完美解决方案。
  3. 虽然zoom的性能比scale差,但是对于B端来说这点性能微不足道,更何况还是大屏可视化平台。
  4. 淘汰rem的理由就一个:它受浏览器字体最小12px的影响。

适配的过程中遇到的问题&解决方案

问题1:

zoom适配过程中,如果是百度或者高德地图或者GIS场景这种作为底图的话,会出现鼠标点击地图有位置偏差。

解决方案

不对 地图/GIS 进行zoom,只对我们自己写的页面或组件的最外层div添加zoom值。高德地图只需要始终充满整个容器

问题2:

zoom适配过程中,遇到类似于Antd 的Select的弹出层,位置不正确

解决方案:

这是因为Antd组件库Select的弹出层默认是直接插在body标签下面的,如果我们的zoom只应用在我们自己写的组件外层div上面的话,那么需要将Select的弹出层挂载到Select组件的父节点身上即可, 这样做的目的是让弹出层也要被zoom。Select组件有个getPopupContainer可以处理这个情况

问题3

zoom 适配过程中,遇到了鼠标悬浮至echarts图表上面的时候,位置出现偏移

解决方案

  • echarts被zoom之后,我们要把echarts 再zoom回去。最开始给页面设置了多少zoom,就要单独给echarts设置了 1 / zoom 的值, 这样操作下来相当于echarts没有被zoom。
  • 此时我们选择scale对echarts进行缩放,给echarts设置transform: scale(zoom的值)&transform-origin: left top,这样就解决了echarts鼠标悬浮或者点击的时候有位置偏差的情况了
  • 可以对echarts进行一次封装,专门处理这个情况,详见github上源代码里面的src/components/YjEcharts组件

问题4

scale 缩放 不保持UI设计稿比例的适配时,果是百度或者高德地图或者GIS场景这种作为底图的话,会出现鼠标点击地图有位置偏差。

解决方案

底图最外层div再次设置scale的值,使得地图最外层div与顶层div设置scale的值的乘积是1即可。假如页面缩放scale的值是0.5的话,那么地图的最外层div要设置scale为 1 / 0.5 = 2

尾声

对于scale缩放页面存在的问题,后续会继续探究,如果能解决会继续更新此文章

github地址

点击查看github源代码

在线演示Demo

点击查看zoom缩放演示案例