如何用SVG画一个特定边框(上)

简介: 如何用SVG画一个特定边框(上)




近的需求中有一个tab切换的场景,设计师提出了自己期望的效果,核心关注点在蓝色边框上,本文围绕如何实现这样的边框效果展开讨论。


背景


设计师期望的效果如下,核心关注点在蓝色边框上。


实现这样的边框,核心问题有几个:

  1. 如何将两个元素的边框相连
  2. 内凹的圆角如何实现
  3. tab元素滚动离屏,边框如何过渡


CSS

我决定先用CSS试试,border + border-radius,应该轻松搞定。

 问题一:CSS 如何实现边框相连


这倒不难,我们需要:

  1. 给 tab元素 设置border-right: none,同时border-top-right-radius: 0border-bottom-right-radius: 0
  2. 再给 tab元素 一个向右的偏移,偏移量 = 边框宽度
  3. 最后让 tab元素 z-Index 高于内容区,并给 tab元素 加上背景色(背景色需要和页面背景色一致)


  • 缺陷


这时候缺点已经来了,我们通过加背景色遮盖边框实现边框相连,不可避免地遮盖了页面内容,如果页面背景比较复杂,我们会很难处理。这个方案并不足够通用,但好在我们的场景页面背景纯白,先忍了。


 问题二:CSS 如何实现内凹圆角


也还行,我们需要:

  1. 新增两个元素,宽高和border-radius值相等
  2. 元素1 设置背景色,先覆盖在边框相连处
  3. 元素2 设置border-bottom-right-radius: 50%,同时border和tab元素保持一致


  • 缺陷


其实和问题一一样,我们又使用了背景色对边框进行遮盖,但先忍了,实现要紧。


 问题三:CSS 如何实现滚动离屏过渡


这个问题用css就比较难实现了,它可以被拆解成两个子问题:

  1. tab元素的顶边在离屏过程中需要固定,border 框选区域高度不断变小
  2. 圆角如何平滑过渡到直线



如果世界上已经没有其他方式能实现这样的边框,我想硬着头皮写一堆恶心逻辑也是能实现效果的,但我觉得这样的实现比较丑陋,不太优雅,因此 CSS 的尝试到这里就结束了,我决定换个方案。
SVG


其实使用SVG来实现一些CSS不好处理的场景在社区中已经有很多实践了。比如用于新人引导的开源库 driver.js

driver.js地址:https://driverjs.com/


【新人引导】指的是这样的场景:


这个场景下,【蒙层内区域高亮】是技术核心,driver.js 在几个月前刚进行了一次重构,将蒙层改用SVG实现,支持了高亮区的圆角。这给了我启发,哥们也用 SVG 画个边框吧。


 问题一:SVG 如何实现边框相连


svg嘛,用起来就是更麻烦,先从简单的开始吧:

  • 怎么用 SVG 画一条线


这个容易,使用 <line /> 标签,提供两个点坐标(x1, y1)、(x2, y2),在描述一下边框的样式就可以了。

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <line x1="0" y1="80" x2="100" y2="20" stroke="black" />
</svg>


使用 line 标签的方式固然可以,但为了方便后续代码逻辑,我们还有更好的方式:<path />标签,我们可以通过命令式的方式,完成 SVG 各种型状的绘制,比如一条直线:

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <path d="M 0 80 L 100 20" stroke="black" fill="none" />
</svg>


<path />文档地址https://developer.mozilla.org/zh-CN/docs/Web/SVG/Tutorial/Paths


其中核心字段位 d="M 0 80 L 100 20",这一段命令中有两个指令 ML

  1. M全称“Move to”,可以理解为将SVG画笔挪到某个点作为路径起点,因此该命令后边跟两个数字,分别对应起点的 x、y
  2. L全称“Line”,可以理解为从当前画笔位置为起点,绘制一条直线到另一个点(终点),并且绘制后,画笔位置也会挪到终点,(path 中大多数指令都是指定终点即可,起点就是当前画笔位置),因此该命令后边跟两个数字,分别对应终点的 x、y


关于 path 的其他指令不再赘述,总的来说,想使用 path 绘制边框,我们首先要获取到边框上各个结点坐标,之后再用命令将他们链接起来。


  • 获取点坐标来画线


我们首先获取 tab元素 和 内容区 的四个节点,我们通过getBoundingClientRect方法获取 topleftrightbottom 四个值来构造这些点坐标。


但我们不能直接给他两点相连起来,那就成这样了:


我们需要做做个调整,需要将(right1, top1)(right1, bottom1)两个点的 x 坐标做偏移,让这两点的 x 和元素2的 left 一致,得到(left2, top1)(left2, bottom1)


我们再给这些点加上编号,按照 ABCDEFGH 的顺序,将这些点通过直线相连,path的命令就会如下:

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <path
    d="
      M left1 top1
      L left2 top1
      L left2 top2
      L right2 top2
      L right2 bottom2
      L left2 bottom2
      L left2 bottom1
      L left1 bottom1
      Z
    "
    stroke="black"
    fill="none"
  />
  <!-- Z 命令为 path 结束指令-->
</svg>


这样实现的边框,不会有 CSS 背景色遮挡的问题。

 问题二:SVG 如何实现内凹圆角


问题又变得复杂起来了,同样,我们还是先从简单的开始吧:


  • 怎么用 SVG 画一个圆弧


path 中有一个弧形指令A,这个指令能绘制椭圆,正圆自然也不在话下,他的参数有很多:

A rx ry x-axis-rotation large-arc-flag sweep-flag x y


rx ry:为 X 轴和 Y 轴的半径,对于正圆来说,rx = ry,在我们的场景里,他的值和 border-radius 是等效的x-axis-rotation:用于控制这个弧线沿 X 轴旋转的角度,对于正圆来说,怎么转都一样,所以这个值我们使用时始终为 0 即可large-arc-flag决定弧线是大于还是小于 180 度,0 表示小角度弧,1 表示大角度弧,由于border-radius 其实都是 90 度角,因此我们使用时始终为 0 即可sweep-flag:表示弧线的方向,0 表示从起点到终点沿逆时针画弧,1 表示从起点到终点沿顺时针画弧x y:弧线终点坐标

下边是一些示例:

<svg width="325" height="325" xmlns="http://www.w3.org/2000/svg">
  <path
    d="M 80 80
      A 45 45, 0, 0, 0, 125 125
      L 125 80 Z"
    fill="green"
  />
  <path
    d="M 230 80
      A 45 45, 0, 1, 0, 275 125
      L 275 80 Z"
    fill="red"
  />
  <path
    d="M 80 230
      A 45 45, 0, 0, 1, 125 275
      L 125 230 Z"
    fill="purple"
  />
  <path
    d="M 230 230
      A 45 45, 0, 1, 1, 275 275
      L 275 230 Z"
    fill="blue"
  />
</svg>


效果如下(有颜色区域是最终形状,其他线条是辅助线):


  • 给边框加上圆角


上文中,我们已经拿到了 ABCDEFGH 8个点,每一个点其实都会有一个对应的圆弧,因此在绘制边框的时候,我是这样管理 圆弧 和 直线 的,下边是一个点的数据结构:

const A = {
  x: 100,
  y: 100,
  arc: 'A xxxxxx', // 经过该点的圆弧
  line: 'L xxxxxx' // 圆弧的结束点到下一个圆弧起点的直线
}


根据这个结构,我再按 ABCDEFGH 的顺序,将每个点的 svg 指令拼接起来,先拼接 圆弧(arc) 再拼接 直线(line)


那么圆弧的指令如何生成呢,我们以一个点来分析:


  1. 圆弧的起点坐标为(x, y-radius)
  2. 终点坐标为(x+radius, y)
  3. 半径就是 border-radius 的值
  4. 弧线方向会有区别,两个内凹圆角是逆时针其他圆角都是顺时针


有了这些信息,其实一个圆弧的指令就呼之欲出了,我们通过一段代码快速生成(两个为 0 的值上文介绍A指令时有提到,不赘述原因):

enum ESweepFlag {
  cw = 1, // 顺时针
  ccw = 0, // 逆时针
}

/**
 * 生成圆弧svg路径
 * @param endX: 圆弧终点x坐标
 * @param endY: 圆弧终点y坐标
 * @param radius: 圆弧半径
 * @param sweepFlag: 顺时针还是逆时针: 1 顺时针、0 逆时针
 */
const generatorArc = (endX: number, endY: number, radius: number, sweepFlag: ESweepFlag = ESweepFlag.cw) => {
  return `A${radius} ${radius} 0 0 ${sweepFlag} ${endX} ${endY}`;
}


到这里,我们将 圆弧 和 直线 指令,按 ABCDEFGH 点顺序,先圆弧后直线挨个拼接起来,边框也就画成了。


如何用SVG画一个特定边框(下):https://developer.aliyun.com/article/1480476

目录
相关文章
|
6月前
|
算法 前端开发
如何用SVG画一个特定边框(下)
如何用SVG画一个特定边框(下)
72 3
|
6月前
|
前端开发
前端 CSS 经典:SVG 描边动画
前端 CSS 经典:SVG 描边动画
185 0
|
6月前
|
前端开发
【CSS进阶】使用CSS gradient制作绚丽渐变纹理背景效果(中)
【CSS进阶】使用CSS gradient制作绚丽渐变纹理背景效果
164 1
|
6月前
|
前端开发
【CSS进阶】使用CSS gradient制作绚丽渐变纹理背景效果(下)
【CSS进阶】使用CSS gradient制作绚丽渐变纹理背景效果
178 1
|
6月前
|
前端开发 容器
【CSS进阶】使用CSS gradient制作绚丽渐变纹理背景效果(上)
【CSS进阶】使用CSS gradient制作绚丽渐变纹理背景效果
145 1
|
6月前
|
前端开发
CSS圆角大杀器,使用滤镜构建圆角及波浪效果
CSS圆角大杀器,使用滤镜构建圆角及波浪效果
69 0
|
6月前
|
前端开发
css绘制常见的一些图形
css绘制常见的一些图形
30 0
|
前端开发
【前端切图】CSS文字渐变和背景渐变
【前端切图】CSS文字渐变和背景渐变
71 0
|
前端开发
通过构建背景图学习CSS径向渐变
通过构建背景图学习CSS径向渐变
62 0
|
前端开发
前端 SVG 与 Canvas 框架案例 (画线、矩形、箭头、文字 ....)
前端 SVG 与 Canvas 框架案例 (画线、矩形、箭头、文字 ....)
138 0