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

简介: 闲来无事,配合CSS变量搞个Vue数据显示组件吧~

开篇


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


最后的效果图就长这样儿~


网络异常,图片无法展示
|


忽略掉 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. 可能是文本或者数字,数字的话需要处理小数位两位小数


  1. 文字颜色不一样


  1. 文字有的会发光(阴影效果),有的没有


  1. 文字大小也有多中尺寸


所以大致设置了以下 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 />


网络异常,图片无法展示
|


当然,后面也可以增加其他配置,比如单位与文本的间隔、高亮颜色、立体文本?等


WeightedLineBar 权重占比图


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


首先一样是 分析下需求


  1. 底部的 bar 的背景色不确定


  1. bar 的颜色也不确定,也可能是渐变


  1. 数值和最大值都不确定


  1. 文字大小、文本颜色也不确定


  1. 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 样式,也方便后面进行样式排查吧。当然这些都是我的个人感受,也不知道这样做对浏览器的渲染性能有没有影响,不过目前看起来感觉还不错~


目录
相关文章
|
5天前
|
数据采集 存储 前端开发
Puppeteer教程:使用CSS选择器点击和爬取动态数据
本文介绍如何使用Puppeteer结合CSS选择器爬取动态网页数据,以贝壳网的二手房价格为例,通过代理IP提高爬虫成功率。文章详细讲解了Puppeteer的安装和配置、代码实现及数据趋势分析,帮助读者掌握动态网页爬取技术。
Puppeteer教程:使用CSS选择器点击和爬取动态数据
|
11天前
|
前端开发 JavaScript
如何在 JavaScript 中访问和修改 CSS 变量?
【10月更文挑战第28天】通过以上方法,可以在JavaScript中灵活地访问和修改CSS变量,从而实现根据用户交互、页面状态等动态地改变页面样式,为网页添加更多的交互性和动态效果。在实际应用中,可以根据具体的需求和场景选择合适的方法来操作CSS变量。
|
11天前
|
前端开发 JavaScript 数据处理
CSS 变量的作用域和 JavaScript 变量的作用域有什么不同?
【10月更文挑战第28天】CSS变量和JavaScript变量虽然都有各自的作用域概念,但由于它们所属的语言和应用场景不同,其作用域的定义、范围、覆盖规则以及与其他语言特性的交互方式等方面都存在明显的差异。理解这些差异有助于更好地在Web开发中分别运用它们来实现预期的页面效果和功能逻辑。
|
4天前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
|
4天前
|
存储 JavaScript
Vue 组件间如何通信
Vue组件间通信是指在Vue应用中,不同组件之间传递数据和事件的方法。常用的方式有:props、自定义事件、$emit、$attrs、$refs、provide/inject、Vuex等。掌握这些方法可以实现父子组件、兄弟组件及跨级组件间的高效通信。
|
11天前
|
前端开发 JavaScript
如何在 CSS 变量中使用函数?
【10月更文挑战第28天】虽然CSS变量本身不能像传统编程语言中的函数那样直接进行复杂的运算和逻辑处理,但通过CSS预处理器、`calc()` 函数以及与JavaScript的结合,可以在很大程度上实现类似函数的功能,提高CSS样式的灵活性和可维护性,满足各种不同的页面设计和交互需求。
|
11天前
|
前端开发 开发者 容器
CSS 变量的作用域是什么?
【10月更文挑战第28天】理解CSS变量的作用域规则对于有效地使用CSS变量来组织和管理页面样式非常重要。通过合理地利用全局作用域和局部作用域,以及掌握变量的覆盖和继承规则,可以创建更具可维护性、灵活性和可扩展性的CSS样式表,实现各种复杂的页面设计和样式需求。
|
11天前
|
前端开发 JavaScript UED
|
21天前
|
缓存 JavaScript UED
Vue 的动态组件与 keep-alive
【10月更文挑战第19天】总的来说,动态组件和 `keep-alive` 是 Vue.js 中非常实用的特性,它们为我们提供了更灵活和高效的组件管理方式,使我们能够更好地构建复杂的应用界面。深入理解和掌握它们,以便在实际开发中能够充分发挥它们的优势,提升我们的开发效率和应用性能。
43 18
|
11天前
|
前端开发 JavaScript UED
如何使用 JavaScript 动态修改 CSS 变量的值?
【10月更文挑战第28天】使用JavaScript动态修改CSS变量的值可以为页面带来更丰富的交互效果和动态样式变化,根据不同的应用场景和需求,可以选择合适的方法来实现CSS变量的动态修改,从而提高页面的灵活性和用户体验。

热门文章

最新文章