实现一个玻璃拟态的容器组件

2,622 阅读2分钟

这是我参与11月更文挑战的第15天,活动详情查看:2021最后一次更文挑战

背景

首先简单的看两张图片 image.png

image.png

总结一下玻璃拟态的风格:这种把阴影、透明度以及模糊背景结合到一起的UI设计思路,因为给人以玻璃的质感,被Michal Malewicz称为Glassmorphism(玻璃拟态)。

玻璃拟态的背景具有高强度的对比性,加上背景的模糊效果使得样式更加的好看。

vue 组件

首先,我们在 Components下面创建Glassmorphism的组件

<template>
  <div class="Glassmorphism relative">
    <slot />
  </div>
</template>

组件由一个div包裹,然后将组件的内容中心交给slot进行分发。

于是我们对Glassmorphism样式进行处理。

首先我们定义组件的属性。将组件的宽高和圆角设置成可自定义。

const props = withDefaults(
  defineProps<{
    width?: string | number
    height?: string | number
    radius?: string | number
  }>(),
  {
    width: '100%',
    height: '100%',
    radius: 12
  }
)

然后我们对属性值进行一个初步的校验。

const c_width = computed(() => addUnit(props.width))
const c_height = computed(() => addUnit(props.height))
const c_radius = computed(() => addUnit(props.radius))

可以看到我们使用了addUnit进行的规则校验,我们看看addUnit怎么写的吧

export const isDef = (val: any) => val !== undefined && val !== null //是否有值

export const isNumeric = (val: any) => /^\d+(\.\d+)?$/.test(val) // 是否是一个数字

export const addUnit = (val: any) => { // 默认添加px的单位
  if (!isDef(val)) {
    return undefined
  }
  val = String(val)
  return isNumeric(val) ? val + 'px' : val
}

主要由上面几个工具函数组成

于是我们对css进行开发。

<style lang="scss" scoped>
$circlePath: calc(v-bind(c_radius) * 2);
$radius: v-bind(c_radius);
.Glassmorphism {
  width: v-bind(c_width);
  height: v-bind(c_height);
  border-radius: $radius;
  background: linear-gradient(
    to right bottom,
    rgba(255, 255, 255, 0.6),
    rgba(255, 255, 255, 0.1)
  );
  backdrop-filter: blur(6px);
  box-shadow: 0.8rem 0.8rem 1.3rem 0 rgba(0, 0, 0, 0.15);
  
  &::before,
  &::after {
    position: absolute;
    content: "";
    display: block;
    z-index: 2;
    border-radius: $radius;
  }
  &::before {
    width: calc(100% - v-bind(c_radius) * 2);
    height: 1px;
    top: 0;
    left: $radius;
    background: linear-gradient(to right, #fff, transparent);
  }
  &::after {
    width: 1px;
    height: calc(100% - v-bind(c_radius) * 2);
    top: $radius;
    left: 0;
    background: linear-gradient(to bottom, #fff, transparent);
  }
}
</style>

这里其实我们可以看到,我们对容器的上,左边框进行的渐变处理,可是渐变遇上了圆角使得渐变并没有完全被处理掉。 于是我们有可以新增一个辅助dom进行样式微调

于是有:

<div class="Glassmorphism relative">
    <div class="circle absolute top-0 left-0"></div> // 新增
    <slot />
</div>
.circle {
    width: $circlePath;
    height: $circlePath;
    border-radius: 50%;
    border: 1px solid #fff;
    clip: rect(0, $radius, $radius, 0);
    z-index: -1;
}

我们使用clip进行裁剪,尺寸为radius的大小,这样就完美了。

附上源码:

<template>
  <div class="Glassmorphism relative">
    <div class="circle absolute top-0 left-0"></div>
    <slot />
  </div>
</template>

<script lang="ts" setup>
import { addUnit } from '@/utils'
const props = withDefaults(
  defineProps<{
    width?: string | number
    height?: string | number
    radius?: string | number
  }>(),
  {
    width: '100%',
    height: '100%',
    radius: 12
  }
)
const c_width = computed(() => addUnit(props.width))
const c_height = computed(() => addUnit(props.height))
const c_radius = computed(() => addUnit(props.radius))
</script>

<style lang="scss" scoped>
$circlePath: calc(v-bind(c_radius) * 2);
$radius: v-bind(c_radius);
.Glassmorphism {
  width: v-bind(c_width);
  height: v-bind(c_height);
  border-radius: $radius;
  background: linear-gradient(
    to right bottom,
    rgba(255, 255, 255, 0.6),
    rgba(255, 255, 255, 0.1)
  );
  backdrop-filter: blur(6px);
  box-shadow: 0.8rem 0.8rem 1.3rem 0 rgba(0, 0, 0, 0.15);
  > .circle {
    width: $circlePath;
    height: $circlePath;
    border-radius: 50%;
    border: 1px solid #fff;
    clip: rect(0, $radius, $radius, 0);
    z-index: -1;
  }
  &::before,
  &::after {
    position: absolute;
    content: "";
    display: block;
    z-index: 2;
    border-radius: $radius;
  }
  &::before {
    width: calc(100% - v-bind(c_radius) * 2);
    height: 1px;
    top: 0;
    left: $radius;
    background: linear-gradient(to right, #fff, transparent);
  }
  &::after {
    width: 1px;
    height: calc(100% - v-bind(c_radius) * 2);
    top: $radius;
    left: 0;
    background: linear-gradient(to bottom, #fff, transparent);
  }
}
</style>

大家可以自己copy过去,本地尝试,百分百有效,我这里就暂时不运行项目给大家展示了。

并且请注意使用:因为玻璃拟态具有高强度的颜色对比性。需将页面背景设置为暖色系的渐变色效果更佳哦。