持续创作,加快生长!这是我参与「日新方案 10 月更文应战」的第N天,点击检查活动详情

开篇

由于这两天手里的项目算是整完啦,所以有点空闲来搞搞好玩儿的东西。自身这个跟上一篇闲来无事,弄个“纯CSS”的伪3D柱状图吧~算是姊妹篇,由于都是从UI图上抠下来的。

最后的作用图就长这样儿~

闲来无事,合作CSS变量搞个Vue数据显现组件吧~

疏忽掉 demo 里面的文本和单位哈,由于右边的数值显现 部分的款式在其他地方也有类似的作用,所以拆分成了两个组件:HighlightText 高亮文本组件 和 WeightedLineBar 权重占比图

也为了练习一下 CSS 变量,所以有一部分动态款式 使用了Vue 动态特点合作 CSS 变量来完成的。 假如有兴趣的同学能够接着往下看,代码和逻辑都很简略,对稍微凶猛一点的同学可能都没有太大帮助哈,实在抱愧!

HighlightText 高亮文本

由于组件的主要作用便是 调整文本的款式,所以组件内部只要一个span标签,经过 核算特点 动态设置标签的款式和类名。

<template>
  <span
    :class="['highlight-text', { 'with-unit': !!unit, lighting: lighting }]"
    v-bind="{ 'data-attr-unit': !!text && unit }"
    :style="computedStyle"
    >{{ computedText }}</span
  >
</template>

当然,好像是由于 html 的解析问题,假如span之类的 标签与文本不在同一行或许没有紧邻的情况下,文本两头会出现空白字符占位。所以这儿格式化成了这个姿态。

而其间的 自界说特点 data-attr-unit,则是为了便利后边显现单位。

分析咱们UI图中的几个场景,大致发现了以下几个需求:

  1. 可能是文本或许数字,数字的话需求处理小数位两位小数
  2. 文字颜色不相同
  3. 文字有的会发光(暗影作用),有的没有
  4. 文字大小也有多中尺寸

所以大致设置了以下 props 装备项:

export default {
  name: "HighlightText",
  props: {
    text: {
      type: [String, Number],
      default: ""
    },
    color: {
      type: String,
      default: "#010101" // #BCE3FF
    },
    size: {
      type: [String, Number],
      default: 12
    },
    separator: {
      type: Boolean,
      default: true
    },
    bolder: {
      type: Boolean,
      default: true
    },
    lighting: {
      type: Boolean,
      default: true
    },
    unit: {
      type: String,
      default: ""
    }
  }
}

在上面的需求上又增加了separatorbolder装备,用来确认数字是否需求分隔符和文本部分的文字字重(已然都抽离成组件了,装备多一点没毛病吧~)。

然后,便是处理模板所用到的核算特点部分了。

export default {
  computed: {
    computedText() {
      if (typeof this.text === "number" && this.separator) {
        return this.text.toLocaleString();
      }
      return this.text;
    },
    computedStyle() {
      let fontSize = this.size;
      let fontWeight = "normal";
      const color = this.color;
      if (typeof fontSize === "number") {
        fontSize += "px";
      }
      if (this.bolder) {
        fontWeight = "bold";
      }
      return { fontSize, fontWeight, color, lineHeight: fontSize };
    }
  }
};

数字分隔符的话,自身 Number 供给了一个toLocalString() 的办法,用来转成 带逗号分隔的字符串;当然为了便利,这儿对款式部分就仅仅做了一点组装然后绑定成行内款式的方式给了span 标签。

然后便是 CSS 部分。嗯~~~这部分太简略,就直接放代码吧。仅仅留意unit单位为了节省标签,选用的是自界说标签特点的方式结合伪类来完成的。

.highlight-text {
  font-family: Akrobat-Black, Akrobat, sans-serif;
  user-select: none;
  word-break: break-word;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  &.lighting {
    text-shadow: 0 0 6px;
  }
  &.with-unit {
    &::after {
      content: attr(data-attr-unit);
      display: inline-block;
      transform: scale(0.4);
      transform-origin: bottom left;
    }
  }
}

最终咱们能够经过这样的代码得到一些理想的作用:

  <highlight-text text="这是高亮文本" size="24px" unit="unit" /><br />
  <highlight-text :text="8972183671.1762" size="32px" unit="%" /><br />
  <highlight-text text="这是高亮文本2" size="32px" color="#ea28bc" /><br />
  <highlight-text text="这是高亮文本3,没有单位" size="48px" color="#8b5fda" /><br />
  <highlight-text text="这是高亮文本4" size="64px" color="#ea28bc" unit="%" :lighting="false" /><br />

闲来无事,合作CSS变量搞个Vue数据显现组件吧~

当然,后边也能够增加其他装备,比方单位与文本的间隔、高亮颜色、立体文本?等

WeightedLineBar 权重占比图

这个其实自身只要上面的title标题和下面的占比比例显现两个部分,封面的图是多个组合在一起完成的。

首要相同是 分析下需求

  1. 底部的 bar 的背景色不确认
  2. bar 的颜色也不确认,也可能是突变
  3. 数值和最大值都不确认
  4. 文字大小、文本颜色也不确认
  5. bar 的高度也不确认

所以收拾一下,开始确认的 Props 装备大概有以下项目:

export default {
  props: {
    data: {
      type: Number,
      default: 0
    },
    max: {
      type: Number,
      default: 1
    },
    barHeight: {
      type: Number,
      default: 16
    },
    barPadding: {
      type: Number,
      default: 2
    },
    icon: {
      type: String,
      default: "1"
    },
    title: {
      type: String,
      default: ""
    },
    unit: {
      type: String
    },
    highlight: {
      type: Boolean,
      default: true
    },
    fontColor: {
      type: String,
      default: "#ffffff"
    },
    fontSize: {
      type: [String, Number],
      default: "20px"
    },
    color: {
      type: [String, Array],
      default: "#92e1fe"
    },
    bgColor: {
      type: String,
      default: "#33414c"
    }
  }
  }
};

然后便是大致设计一下html 模板和 CSS 基础款式,首要模板部分如下

<template>
  <div class="weighted-line-bar">
    <div class="bar__header" :style="computedHeaderStyle">
      <slot name="icon"></slot>
      <div v-if="!$slots.icon" class="bar__header-icon">{{ icon }}</div>
      <div class="bar__header-title">{{ title }}</div>
      <div v-if="!highlight" class="bar__header-data" :data-attr-unit="unit">{{ data }}</div>
      <highlight-text v-else :text="data" :unit="unit" :color="fontColor" :size="fontSize" />
    </div>
    <div class="bar__content" :style="computedBoxStyle">
      <div :class="computedBarClass" :style="computedBarStyle"></div>
    </div>
    <slot name="footer" />
  </div>
</template>

为了便利布局,外层选用的是笔直散布的 flex 布局,内部分为 两层:header 和 content,底部留了一个slot 插槽,用来显现扩展内容。

然后header 部分的 Icon 图标方位,也供给了一个自界说插槽,没有的话则能够传递参数使用默许款式显现header 相同是 flex 布局,中心的 title 标题部分使用 flex: 1 占据除 icon 和 data 两个部分之外的剩下区域。

也是之前在他人的代码中看到能够经过行内款式设置 CSS 变量,在外部 style 中使用,所以脑子一热准备用computed 合作 v-bind:style 来完成动态界说 CSS 变量,看能不能完成。

最终的computed 核算特点部分和 style 款式部分由于联系比较紧密,就不拆分代码了,完好的代码如下:

<template>
  <div class="weighted-line-bar">
    <div class="bar__header" :style="computedHeaderStyle">
      <slot name="icon"></slot>
      <div v-if="!$slots.icon" class="bar__header-icon">{{ icon }}</div>
      <div class="bar__header-title">{{ title }}</div>
      <div v-if="!highlight" class="bar__header-data" :data-attr-unit="unit">{{ data }}</div>
      <highlight-text v-else :text="data" :unit="unit" :color="fontColor" :size="fontSize" />
    </div>
    <div class="bar__content" :style="computedBoxStyle">
      <div :class="computedBarClass" :style="computedBarStyle"></div>
    </div>
    <slot name="footer" />
  </div>
</template>
<script>
import { getRawType } from "../../utils/tools";
import HighlightText from "@/components/HighlightText";
export default {
  name: "WeightedLineBar",
  components: { HighlightText },
  props: {
    data: {
      type: Number,
      default: 0
    },
    max: {
      type: Number,
      default: 1
    },
    barHeight: {
      type: Number,
      default: 16
    },
    barPadding: {
      type: Number,
      default: 2
    },
    icon: {
      type: String,
      default: "1"
    },
    title: {
      type: String,
      default: ""
    },
    unit: {
      type: String
    },
    highlight: {
      type: Boolean,
      default: true
    },
    fontColor: {
      type: String,
      default: "#ffffff"
    },
    fontSize: {
      type: [String, Number],
      default: "20px"
    },
    color: {
      type: [String, Array],
      default: "#92e1fe"
    },
    bgColor: {
      type: String,
      default: "#33414c"
    }
  },
  computed: {
    computedBoxStyle() {
      let styles = {};
      styles["--box-color"] = this.bgColor;
      styles["--box-height"] = `${this.barPadding * 2 + this.barHeight}px`;
      styles["--bar-width"] = `${(this.data / this.max) * 100}%`;
      styles["--bar-padding"] = `${this.barPadding}px`;
      styles["--bar-height"] = `${this.barHeight}px`;
      return styles;
    },
    computedHeaderStyle() {
      let styles = {};
      styles["--font-color"] = this.fontColor;
      if (typeof this.fontSize === "number") {
        styles["--font-size"] = this.fontSize + "px";
      } else {
        styles["--font-size"] = this.fontSize;
      }
      return styles;
    },
    computedBarClass() {
      let classes = "bar__content-inner";
      if (
        getRawType(this.color) === "array" ||
        this.color.startsWith("radial") ||
        this.color.startsWith("linear") ||
        this.color.startsWith("repeating")
      ) {
        classes += " gradient-bg";
      }
      return classes;
    },
    computedBarStyle() {
      let styles = {};
      if (this.computedBarClass === "bar__content-inner") {
        styles["--bar-color"] = this.color;
      } else {
        if (getRawType(this.color) === "array") {
          styles["backgroundImage"] = `linear-gradient(to right, ${this.color.join(",")})`;
          styles["--bar-color"] = this.color[0];
        } else {
          styles["backgroundImage"] = this.color;
          styles["--bar-color"] = "#92e1fe";
        }
      }
      return styles;
    }
  }
};
</script>
<style scoped lang="scss">
.weighted-line-bar {
  width: 100%;
  height: 100%;
  display: grid;
  grid-template-rows: 1fr 1fr;
  grid-row-gap: 12px;
}
.bar__header {
  width: 100%;
  height: 100%;
  display: flex;
  flex-wrap: nowrap;
  justify-content: space-between;
  align-items: center;
}
.bar__header-icon,
.bar__header-title,
.bar__header-data {
  color: var(--font-color);
  font-size: var(--font-size);
  overflow: hidden;
  word-break: break-word;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bar__header-icon {
  border: 2px solid var(--font-color);
  padding: 0 4px 0 0;
}
.bar__header-title {
  flex: 1;
  text-align: left;
  padding-left: 1em;
}
.bar__header-data {
  padding-left: 1em;
  font-weight: bold;
  &::after {
    content: attr(data-attr-unit);
    display: inline-block;
    transform: scale(0.4);
    transform-origin: bottom left;
  }
}
.bar__content {
  width: 100%;
  height: var(--box-height);
  border-radius: calc(var(--box-height) / 2);
  background-color: var(--box-color);
  position: relative;
  .bar__content-inner {
    position: absolute;
    left: calc(var(--bar-padding) - 1px);
    top: var(--bar-padding);
    width: var(--bar-width);
    height: var(--bar-height);
    border-radius: calc(var(--bar-height) / 2);
    background-color: var(--bar-color);
    box-shadow: 0 0 calc(var(--bar-padding) + 4px) 0 var(--bar-color);
    transition: all ease-in-out 0.2s;
  }
}
</style>

发现的小技巧

上面说了有用 computed 合作 v-bind:style 来完成动态界说 CSS 变量,然后在外部 style 中使用的方式,在我写完的第一眼发现看上去好像是没什么用,但是后边再改改又发现,还是有那么一点儿用的。

比方咱们在 header 部分的外层 div 绑定的动态款式声明的 CSS 变量 --font-size--font-color,那么咱们在这个 div 的 所有子元素中,都能够用这两个变量,这样也算是能够 减少咱们去绑定多个动态款式 吧,而且我发现,这样写 也减少了行内款式的问题,能够直接在下面的 style 部分直接写完完好的 css 款式,也便利后边进行款式排查吧。当然这些都是我的个人感触,也不知道这样做对浏览器的渲染功能有没有影响,不过目前看起来感觉还不错~

往期精彩

Bpmn.js 进阶指南

Vue 2 源码阅读理解

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

基于 Vue 2 与 高德地图 2.0 的“线面编辑器”