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


目录
相关文章
|
23天前
|
缓存 JavaScript UED
Vue3中v-model在处理自定义组件双向数据绑定时有哪些注意事项?
在使用`v-model`处理自定义组件双向数据绑定时,要仔细考虑各种因素,确保数据的准确传递和更新,同时提供良好的用户体验和代码可维护性。通过合理的设计和注意事项的遵循,能够更好地发挥`v-model`的优势,实现高效的双向数据绑定效果。
126 64
|
1月前
|
数据采集 存储 前端开发
Puppeteer教程:使用CSS选择器点击和爬取动态数据
本文介绍如何使用Puppeteer结合CSS选择器爬取动态网页数据,以贝壳网的二手房价格为例,通过代理IP提高爬虫成功率。文章详细讲解了Puppeteer的安装和配置、代码实现及数据趋势分析,帮助读者掌握动态网页爬取技术。
Puppeteer教程:使用CSS选择器点击和爬取动态数据
|
23天前
|
前端开发 JavaScript 测试技术
Vue3中v-model在处理自定义组件双向数据绑定时,如何避免循环引用?
Web 组件化是一种有效的开发方法,可以提高项目的质量、效率和可维护性。在实际项目中,要结合项目的具体情况,合理应用 Web 组件化的理念和技术,实现项目的成功实施和交付。通过不断地探索和实践,将 Web 组件化的优势充分发挥出来,为前端开发领域的发展做出贡献。
29 8
|
24天前
|
存储 移动开发 前端开发
高效的 HTML 与 CSS 编写技巧,涵盖语义化标签、文档结构优化、CSS 预处理、模块化设计、选择器优化、CSS 变量、媒体查询等内容
本文深入探讨了高效的 HTML 与 CSS 编写技巧,涵盖语义化标签、文档结构优化、CSS 预处理、模块化设计、选择器优化、CSS 变量、媒体查询等内容,旨在提升开发效率、网站性能和用户体验。
36 5
|
23天前
|
JavaScript
在 Vue 3 中,如何使用 v-model 来处理自定义组件的双向数据绑定?
需要注意的是,在实际开发中,根据具体的业务需求和组件设计,可能需要对上述步骤进行适当的调整和优化,以确保双向数据绑定的正确性和稳定性。同时,深入理解 Vue 3 的响应式机制和组件通信原理,将有助于更好地运用 `v-model` 实现自定义组件的双向数据绑定。
|
1月前
|
监控 JavaScript 算法
深度剖析 Vue.js 响应式原理:从数据劫持到视图更新的全流程详解
本文深入解析Vue.js的响应式机制,从数据劫持到视图更新的全过程,详细讲解了其实现原理和运作流程。
|
1月前
|
前端开发 JavaScript
如何在 JavaScript 中访问和修改 CSS 变量?
【10月更文挑战第28天】通过以上方法,可以在JavaScript中灵活地访问和修改CSS变量,从而实现根据用户交互、页面状态等动态地改变页面样式,为网页添加更多的交互性和动态效果。在实际应用中,可以根据具体的需求和场景选择合适的方法来操作CSS变量。
|
1月前
|
前端开发 JavaScript 数据处理
CSS 变量的作用域和 JavaScript 变量的作用域有什么不同?
【10月更文挑战第28天】CSS变量和JavaScript变量虽然都有各自的作用域概念,但由于它们所属的语言和应用场景不同,其作用域的定义、范围、覆盖规则以及与其他语言特性的交互方式等方面都存在明显的差异。理解这些差异有助于更好地在Web开发中分别运用它们来实现预期的页面效果和功能逻辑。
|
1月前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
|
1月前
|
存储 JavaScript
Vue 组件间如何通信
Vue组件间通信是指在Vue应用中,不同组件之间传递数据和事件的方法。常用的方式有:props、自定义事件、$emit、$attrs、$refs、provide/inject、Vuex等。掌握这些方法可以实现父子组件、兄弟组件及跨级组件间的高效通信。
下一篇
DataWorks