CSS特殊形状+阴影制作方法:矩形内凹半圆

效果图:

基础版本(绝对尺寸)

基本要点:

  1. 使用svg mask(遮罩)实现形状的裁剪。
  2. 使用filter: drop-shadow实现裁剪后图形的阴影。
  3. svg使用绝对定位+不可选中避免影响文档流和防止干扰用户操作。(不能隐藏,隐藏后遮罩失效,包括display:nonevisibility: hidden)可以将透明度改为0。
 <svg width="320" height="160" style="position: absolute; user-select: none">
    <defs>
      <mask id="rounded-grooves-mask" maskUnits="userSpaceOnUse" maskContentUnits="userSpaceOnUse">
        <!-- 整体保留区域:白色填充的矩形 -->
        <rect x="0" y="0" width="320" height="180" fill="white" />

        <!-- 要裁剪掉的两个圆(黑色会被“挖掉”) -->
        <circle cx="0" cy="90" r="40" fill="black" />
        <circle cx="320" cy="90" r="40" fill="black" />
      </mask>
    </defs>
  </svg>

  <div class="card-container" ref="card-area">
    <div class="card"></div>
  </div>
.card-container {
  filter: drop-shadow(var(--shadow));
  width: fit-content;
  height: fit-content;
}
.card {
  width: 320px;
  height: 180px;
  padding: 40px;
  background-color: white;
  mask: url(#rounded-grooves-mask);
  mask-composite: exclude;
}

此种方法必须手动设置、更新svg尺寸、遮罩尺寸、圆心位置和半径,仅实现了“先裁剪,再添加阴影”的效果。

进阶版本(动态尺寸)

改进点:

  1. 使用ResizeObserver监听内容元素尺寸变化,更新响应式变量,svg中与尺寸相关的均绑定为响应式变量。
  2. 圆半径使用响应式变量。

完整vue3代码:

<template>
  <svg :width="size.x" :height="size.y" style="position: absolute; user-select: none">
    <defs>
      <mask id="rounded-grooves-mask" maskUnits="userSpaceOnUse" maskContentUnits="userSpaceOnUse">
        <rect x="0" y="0" :width="size.x" :height="size.y" fill="white" />
        <circle cx="0" :cy="size.y / 2" :r="props.radius" fill="black" />
        <circle :cx="size.x" :cy="size.y / 2" :r="props.radius" fill="black" />
      </mask>
    </defs>
  </svg>

  <div
    class="card-container"
    ref="card-area"
    :style="{
      '--shadow': props.shadow,
    }"
  >
    <div class="card"></div>
  </div>
</template>

<script setup>
import { onBeforeUnmount, onMounted, ref, useTemplateRef } from "vue";

const props = defineProps({
  radius: {
    type: Number,
    default: 40,
  },
  shadow: {
    type: String,
    default: "0px 0px 5px #929598",
  },
});
const cardArea = useTemplateRef("card-area");

const size = ref({
  x: 0,
  y: 0,
});

let observer = null;
onMounted(() => {
  observer = new ResizeObserver((entries) => {
    for (const entry of entries) {
      const { width, height } = entry.contentRect;
      size.value = {
        x: width,
        y: height,
      };
    }
  });
  if (cardArea.value) {
    observer.observe(cardArea.value);
  }
});

onBeforeUnmount(() => {
  observer?.disconnect();
});
</script>

<style scoped>
.card-container {   
  filter: drop-shadow(var(--shadow));
  width: fit-content;
  height: fit-content;
}
.card {
  width: 320px;
  height: 180px;
  background-color: white;
  mask: url(#rounded-grooves-mask);
  mask-composite: exclude;
}
</style>

内容区域设置padding,使用插槽即可。

不可行的方法

box-shadow + clip-path

clip-path是对元素的“最终图像”的裁剪,使用box-shadow创建的阴影会被一并裁剪,导致阴影在裁剪的地方不显示。

clip-path:path() 或 svg + clip-path:url()

使用path或svg中的clipPath绘制路径时,不能正确处理第二个圆形的填充规则。设置fill-rule="evenodd"后,依然不能正确处理叠加关系,导致左侧半圆效果正常,而右侧半圆与原始图形的效果为“交集”,而非要求的差集。可能和圆形曲线绘制顺序有关,有待测试。

兼容性

https://caniuse.com/?search=mask
https://caniuse.com/?search=filter%3A%20drop-shadow
https://caniuse.com/?search=ResizeObserver

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇