一行指令实现大屏元素分辨率适配(Vue)

4,633 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第N天,点击查看活动详情

一行指令实现大屏元素分辨率适配(Vue)

前言

随着前端技术的不断发展、数据中心(中台)之类的概念的不断升级、物联网设备的更新和普及,越来越多的业主(项目)喜欢在系统中添加一个或者多个可视化大屏,用来集中的展现数据变化、位置变化等等,老板们也更喜欢称之为“态势”。

当然,作为程序员一般都不关心“老板们”的想法,只要完成项目即可。但是经常会有这样的问题:我有一个大屏的模板,但是用户的浏览器分辨率不够,或者有的有书签栏有的没有书签栏,更或者是有的全屏了有的只是小窗口,这样就有了代码对不同分辨率场景下的适配需求了。

1. 常见的适配方案

平时我们使用的 web 端的适配方案,主要有以下几种:

  1. vw/vh 配合百分比实现,让元素根据窗口大小进行自动调整
  2. fontSize 配合 rem 实现“单位宽度”的统一
  3. 根据不同的分辨率范围调整页面布局
  4. 版心布局,配合最小宽度

目前大多数屏幕适配方案的原理都是采用的以上的几种方式,但是这几种方式也有很大的弊端:浏览器文字有最小尺寸!

在一般的 1080p 及以上的分辨率的屏幕中,大多数设计图的比例和显示效果都能完美还原。但如果某个系统的页面内容太多,或者浏览器部分使用的分辨率(不是物理分辨率)达不到完整显示的要求,采用上面的几种方式就有可能造成 文字的计算大小小于浏览器的最小字体大小,此时就有可能因为文字宽度超出元素而导致页面样式崩溃。

版心布局配合最小宽度可以保证显示效果,但是不适合大屏项目。

2. CSS3 缩放方案

在上面的几种方案都不满足时,大家一般就会采用另外一种方案:CSS3 scale 缩放。

通过计算设计图尺寸比例与实际的页面显示区域大小,来动态调整元素的缩放比例。

个人认为这是针对小分辨率情况下保留显示内容及样式最好的一种处理方式。

当然,这种方式依然有一些弊端:

  1. 缩放后可能会造成边缘显示模糊
  2. 如果内部存在 canvas 元素,可能导致 canvas 内部的内容渲染失真
  3. 高德地图 1.x 会导致事件坐标偏移 (2.0 已经修复)
  4. ...

3. 封装一个缩放指令

这里简单回顾一下 Vue 的自定义指令:通过配置自定义指令和绑定参数,在组件/元素加载、更新、销毁等不同时期执行对应的处理逻辑。

Vue 的自定义指令包含一下几个钩子函数:

  • bind: 解析到指令绑定时执行,仅执行一次
  • inserted: 插入父节点时执行
  • update:组件触发更新时执行
  • componentUpdated:所有组件更新结束之后执行
  • unbind:元素解绑(销毁)时执行,也只执行一次

这里因为我们只需要在初始化时绑定浏览器的 resize 事件来调整元素缩放,所以只需要配置 inserted 即可;当然,为了优化代码逻辑,减少资源消耗等情况,也需要在 unbind 阶段去取消 resize 事件的一个回调函数。

代码如下:

// 缩放指令
import Vue from "vue";

function transformScale(el, options) {
  const { target = "width", origin = "top left" } = options;

  Vue.nextTick(() => {
    // 获取显示区域高宽
    const width = window.innerWidth;
    const height = window.innerHeight;
    el.style.transformOrigin = origin;
    if (target === "ratio") {
      const scaleX = width / CONF.width;
      const scaleY = height / CONF.height;
      el.style.transform = `scaleX(${scaleX}) scaleY(${scaleY})`;
    } else {
      let scaleProportion = 1;
      if (target === "width") {
        scaleProportion = width / CONF.width;
      }
      if (target === "height") {
        scaleProportion = height / CONF.height;
      }
      el.style.transform = `scale(${scaleProportion})`;
    }
  });
}

function inserted(el, binding) {
  const options = binding.options || { passive: true };

  const callback = () => transformScale(el, binding.value);

  window.addEventListener("resize", callback);

  callback();

  el._onResize = {
    callback,
    options
  };
}

function unbind(el) {
  if (!el._onResize) {
    return;
  }

  const { callback } = el._onResize;
  window.removeEventListener("resize", callback);
  delete el._onResize;
}

export const Scale = {
  inserted,
  unbind
};

export default Scale;

说明:

  1. 指令接收一个对象参数,用来指定比例计算方式和缩放定位
  2. 需要一个全局配置 CONF 对象,用来指定默认的页面尺寸
  3. 为了保证页面已经加载完,能获取到 dom 元素,需要调用 Vue.nextTick
  4. 需要销毁监听事件

整个代码其实很简单,就是通过监听 resize 事件去调整元素的缩放比例。

但是这里我也做了一点小的配置,用来适应更多的情况:

  1. 接收一个 target 配置,用来确认比例计算方式;可以以宽度或者高度作为统一的缩放标准,也可以分别计算
  2. 接收 transform 的 origin 配置,保证不同位置的元素可以缩放到不同的位置,避免缩放偏移
  3. 不涉及绑定元素的尺寸,只需要默认尺寸即可;写代码时可以直接根据设计图配置元素尺寸

4. 后记

当然,这个指令不能说有多完美,依然有很多有漏洞的地方,比如没有防抖、缩放不会改变css指定的尺寸,容易出现滚动条等;并且因为之前的项目中还涉及到很多图表、地图,也经常导致一些显示问题,所以后面有增加了一些新的指令,但是分辨率适配这个问题还是要根据实际情况来确定具体的方案。