Skip to content
当前页导航

自定义指令 v-click-outside

点击当前元素外部时,会执行传入的函数。一般用于点击其他地方,关闭弹窗或者关闭下拉框,隐藏元素等。

html
<div v-click-outside="hide">
  <button @click="open">打开弹窗</button>
  <div v-if="show">我是弹窗</div>
</div>

<script>
  function open() {
    this.show = true
  }
  function hide() {
    this.show = false
  }
</script>
js
Vue.directive('clickOutside', {
  bind(el, bindings, vnode) {
    el.handler = function (e) {
      if (!el.contains(e.target)) {
        const methed = bindings.expression
        vnode.context[methed]()
      }
    }
    document.addEventListener('click', el.handler)
  },
  unbind(el) {
    document.removeEventListener('click', el.handler)
  },
})

大屏适配

开发可视化数据大屏如何做自适应?vw vh、rem、scale 到底哪种比较好?

适配方案分析

方案实现方式优点缺点
vw + vh(推荐)按照设计稿的尺寸,通过 postcss-px-to-viewport 插件,将 px 转换成 vw 和 vh1、可以动态变更图表宽高、字体等。2、屏幕比列不一致的时候,不会出现留白。1、每个图表都需要单独设置字体、间距等适配。2、postcss-px-to-viewport无法把行内样式中的 px 转换,需要自己写方法。
scale通过 transform: scale 属性,根据屏幕大小,进行整体缩放配置简单、不需要对每个图表进行适配1、当屏幕比例跟设计稿不一样时,可能会出现留白情况。2、弹窗无法对应缩放。3、当缩放比例过大时,图表事件热区会偏移,图表字体会有一点点模糊。4、会导致地图坐标偏移。
rem通过 postcss-pxtorem 插件,自动将 px 转成 rem配置简单、不需要对每个图表进行适配1、当屏幕比例跟设计稿不一样时,可能会出现留白情况。2、每个图表都需要单独设置字体、间距等适配

vw、vh 方案

  1. 通过 postcss-px-to-viewport 插件把 css 文件中的 px 自动转成 vw、vh。
js
// postcss.config.js
module.exports = {
  plugins: [
    postcssPxToViewport({
      unitToConvert: 'px', // 要转化的单位
      viewportWidth: 1920, // UI设计稿的宽度
      unitPrecision: 6, // 转换后的精度,即小数点位数
      propList: [
        'width',
        'left',
        'right',
        'margin-left',
        'margin-right',
        'padding-left',
        'padding-right',
      ], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
      viewportUnit: 'vw', // 指定需要转换成的视窗单位,默认vw
      fontViewportUnit: 'vw', // 指定字体需要转换成的视窗单位,默认vw
      minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
      mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
      replace: true, // 是否转换后直接更换属性值
      exclude: [/node_modules/], // 设置忽略文件,用正则做目录名匹配
      landscape: false, // 是否处理横屏情况
    }),
    postcssPxToViewport({
      unitToConvert: 'px', // 要转化的单位
      viewportWidth: 1080, // UI设计稿的宽度
      unitPrecision: 6, // 转换后的精度,即小数点位数
      propList: [
        'height',
        'line-height',
        'top',
        'bottom',
        'margin-top',
        'margin-bottom',
        'padding-top',
        'padding-bottom',
      ], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
      viewportUnit: 'vh', // 指定需要转换成的视窗单位,默认vw
      fontViewportUnit: 'vh', // 指定字体需要转换成的视窗单位,默认vw
      minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
      mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
      replace: true, // 是否转换后直接更换属性值
      exclude: [/node_modules/], // 设置忽略文件,用正则做目录名匹配
      landscape: false, // 是否处理横屏情况
    }),
  ],
}
  1. 处理行内样式
js
// 定义设计稿的宽高
const designWidth = 1920
const designHeight = 1080

// px转vw
export const px2vw = (_px) => {
  return (_px * 100) / designWidth + 'vw'
}

export const px2vh = (_px) => {
  return (_px * 100) / designHeight + 'vh'
}

export const px2font = (_px) => {
  return (_px * 100) / designWidth + 'vw'
}
  1. 处理图表中的文字及偏移等样式

echarts 的字体大小只支持具体数值(像素),不能用百分比或者 vw 等尺寸,一般字体不会去做自适应,当宽高比跟 ui 稿比例出入太大时,会出现文字跟图表重叠的情况。

所以我们利用当前屏幕宽度跟设计稿宽度的比例,去重新计算 px 值。

js
const fitChartSize = (size, defalteWidth = 1920) => {
  let clientWidth =
    window.innerWidth ||
    document.documentElement.clientWidth ||
    document.body.clientWidth
  if (!clientWidth) return size
  let scale = clientWidth / defalteWidth
  return Number((size * scale).toFixed(3))
}

// echarts 配置项中使用
const options = {
  grid: {
    left: fitChartSize(10),
    right: fitChartSize(20),
  },
  textStyle: {
    fontSize: fitChartSize(14),
  },
}
  1. 窗口大小变化之后,图表自动调整,利用 element-resize-detector 插件,再加上封装自定义指令,实现自动调用 echarts 的 resize 方法。省得每个图表都写一个监听事件。
js
// directive.js
import * as ECharts from 'echarts'
import elementResizeDetectorMaker from 'element-resize-detector'

// 自定义指令:v-chart-resize 或 v-chart-resize="fn"
Vue.directive('chart-resize', {
  bind(el, binding) {
    el['_vue_resize_handler'] = binding.value
      ? binding.value
      : () => {
          let chart = ECharts.getInstanceByDom(el)
          if (!chart) {
            return
          }
          chart.resize()
        }
    // 监听绑定的div大小变化,更新 echarts 大小
    elementResizeDetectorMaker().listenTo(el, el['_vue_resize_handler'])
  },
  unbind(el) {
    // window.removeEventListener("resize", el['_vue_resize_handler']);
    elementResizeDetectorMaker().removeListener(el, el['_vue_resize_handler'])
    delete el['_vue_resize_handler']
  },
})

// <div v-chart-resize class="chart"></div>

这里要注意的是,图表中如果需要 tab 切换动态更新图表数据,在更新数据时一定不要用 echarts 的 dispose 方法先将图表移除,再重新绘制,因为 resize 指令中挂载到的图表实例还是旧的,就监听不到新的 chart 元素的 resize 了,更新数据只需要用 chart 的 setOption 方法重新设置配置项即可。

scale 方案

实现思路:

  • 屏幕宽高比 < 设计稿宽高比,我们需要缩放的比例是屏幕宽度 / 设计稿宽度
  • 屏幕宽高比 > 设计稿宽高比,我们需要缩放的比例是屏幕高度 / 设计稿高度
  • 如何居中,默认动画的基点是元素中心点,但是我们需要设置成左上角 transform-origin: 0 0;

如果我们拿到的设计稿宽高为: 1920 * 1080,而我们的屏幕大小是 1440 * 900,那么 1440/900 = 1.6,1920/1080 = 1.8, 我们需要缩放的比例是:屏幕宽度除以设计稿宽度 = 1440/1920 = 0.75

html
<div class="screen-wrapper">
  <div class="screen" id="screen"></div>
</div>

<style>
  .screen-root {
    height: 100%;
    width: 100%;
  }
  .screen {
    display: inline-block;
    width: 1920px; /*设计稿的宽度*/
    height: 960px; /*设计稿的高度*/
    transform-origin: 0 0;
    position: absolute;
    left: 50%;
    top: 50%;
  }
</style>
js
export default {
  mounted() {
    // 初始化自适应  ----在刚显示的时候就开始适配一次
    handleScreenAuto()
    // 绑定自适应函数   ---防止浏览器栏变化后不再适配
    window.onresize = () => handleScreenAuto()
  },
  destory() {
    window.onresize = null
  },
  methods: {
    // 数据大屏自适应函数
    handleScreenAuto() {
      const designDraftWidth = 1920 //设计稿的宽度
      const designDraftHeight = 960 //设计稿的高度
      // 根据屏幕的变化适配的比例
      const scale =
        document.documentElement.clientWidth /
          document.documentElement.clientHeight <
        designDraftWidth / designDraftHeight
          ? document.documentElement.clientWidth / designDraftWidth
          : document.documentElement.clientHeight / designDraftHeight
      // 缩放比例
      document.querySelector(
        '#screen'
      ).style.transform = `scale(${scale}) translate(-50%, -50%)`
    },
  },
}

也可以偷懒用第三方插件 v-scale-screen

rem 方案

动态设置 html 的 font-size,再利用 postcss-pxtorem 插件把 px 转成 rem

js
;(function init(screenRatioByDesign = 16 / 9) {
  let docEle = document.documentElement
  function setHtmlFontSize() {
    var screenRatio = docEle.clientWidth / docEle.clientHeight
    var fontSize =
      ((screenRatio > screenRatioByDesign
        ? screenRatioByDesign / screenRatio
        : 1) *
        docEle.clientWidth) /
      10
    docEle.style.fontSize = fontSize.toFixed(3) + 'px'
    console.log(docEle.style.fontSize)
  }
  setHtmlFontSize()
  window.addEventListener('resize', setHtmlFontSize)
})()
js
// postcss.config.js
module.exports = {
  plugins: [
    postcssPxToRem({
      rootValue: 16,
      unitPrecision: 3,
      propList: ['font', 'font-size', 'line-height', 'letter-spacing'],
      selectorBlackList: [],
      replace: true,
      mediaQuery: false,
      minPixelValue: 0,
      exclude: /node_modules/i,
    }),
  ],
}