Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSS 精确绘制三角形 #16

Open
JaredCen opened this issue Jun 6, 2019 · 0 comments
Open

CSS 精确绘制三角形 #16

JaredCen opened this issue Jun 6, 2019 · 0 comments

Comments

@JaredCen
Copy link
Owner

JaredCen commented Jun 6, 2019

通常情况下,用 CSS 来实现一些简单的图形会比使用图片更有优势,譬如:

  • CSS 可以随时调整图形的颜色;
  • CSS 可以给图形加复杂动画;
  • 图片会消耗额外的网络资源;
    ...

接下来介绍如何用 CSS 精确绘制三角形及其衍生图形。

盒模型

所有元素都可以被描述为一个个矩形的盒子,盒子由 4 个部分组成:内容区域(content area)内边距区域(padding area)边框区域(border area)外边距区域(margin area)

配图摘自 https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Box_Model/Introduction_to_the_CSS_box_model

一个边框宽度不为零的矩形元素是这样的:

<style>
.box {
  width: 50px;
  height: 50px;
  border-width: 30px;
  border-style: solid;
  border-color: #07C160 #FA5151 #409eff #e6a23c;
}
</style>
<div class="box"></div>

如果把 内容区域 的宽高都置为 0

<style>
.box {
  /*
    ...  
   */
  width: 0;
  height: 0;
}
</style>

此时内容、内外边距区域都为空,盒模型相当于被等宽的边框区域瓜分成四个等腰直角三角形。那么,只需要将其他三个三角形都透明化,我们要的三角形就呼之欲出了。

实心三角形

<style>
.triangle {
  width: 0;
  height: 0;
  border-width: 0 50px 50px 50px;
  border-style: solid;
  border-color: transparent transparent #409eff transparent;
}
</style>
<div class="triangle"></div>

PS:假如我们只需要下方的三角形,那么上边框是没有起任何作用的,不妨也将其置为 0。

我们可以通过 调整各边框的宽度,来控制三角形的形状

<style>
.triangle {
  width: 0;
  height: 0;
  border-width: 0 50px 50px 50px;
  border-style: solid;
  border-color: transparent transparent #409eff transparent;
}
</style>
<div class="triangle"></div>

空心三角形

在实心三角形的基础上,把两个大小不一的三角形叠在一起,就绘制出空心三角形了。

但这里其实是有讲究的,我们通常期望空心三角形的三边宽度是相等的,那么为了符合要求我们需要如何调整两个三角形的位置?偏移量又是多少呢?

这里利用伪元素来绘制第二个三角形:

<style>
.hollow {
  position: relative;
  width: 0;
  height: 0;
  border-width: 0 50px 50px 50px;
  border-style: solid;
  border-color: transparent transparent #409eff transparent;
}
.hollow:before {
  content: '';
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  border-width: 40px;
  border-style: solid;
  border-color: transparent transparent #e6a23c transparent;
}
</style>
<div class="hollow"></div>

对应的模型如下图所示,其中 内三角形 的坐标轴原点位于 外三角形 的顶点处(位于父元素的内容区域)。我们先挪动内三角形使两者的顶点重合,那么内三角形需要左移和上移各$H_{2}$像素。

然后调整内三角形在纵坐标轴上的位置。我们期望的最终效果是这样的

$A_{1}A_{2}$是待求的纵坐标偏移值

$$A_{1}A_{2} = \frac{x}{\sin ∠O} $$

由于内外三角形的三边都是分别平行的,我们可以建立等式

$$H_{1} = A_{1}A_{2} + H_{2} + x =\frac {\sin ∠O + 1}{\sin ∠O}x + H_{2} $$

求出$x$

$$x = \frac{\sin ∠O(H_{1} - H_{2})}{\sin ∠O + 1} $$

所以纵坐标轴上的总偏移量$S$为

$$S = 0 - H_{2} + A_{1}A_{2} = \frac{(H_{1} - H_{2})}{\sin ∠O + 1} - H_{2}$$

假设内外三角形都是等腰直角三角形,则$\sin ∠O = \frac {\sqrt 2}{2}$,那么总偏移量$S$为

$$S = \frac{\frac {\sqrt 2}{2} (H_{1} - H_{2})}{\frac {\sqrt 2}{2} + 1} - H_{2} = (2 - \sqrt 2)(H_{1} - H_{2}) - H_{2}$$

将 demo 中的参数$H_{1} = 50$,$H_{2} = 40$代入方程式,求得

$$S = (2 - \sqrt 2)\times 10-40 \approx -34$$

所以横坐标偏移量为 -40px,纵坐标偏移量为 -34px

箭头

箭头的实现原理和空心三角形如出一辙,只是得顺便把外三角形的下边给覆盖住,因此这里 把 内三角形 的边框宽度调整为与 外三角形 的相等(或者与 外三角形 的边框宽度相近即可)

我们期望的最终模型是这样的

我们最终需要关心的是$x$的值,它代表着箭头的宽度。假设我们需要绘制 1px 宽的箭头

$$x = A_{1}A_{2}\sin ∠O = 1$$

参考对空心三角形纵轴偏移值的计算,纵轴上的总偏移量$S$为

$$S = 0 - H_{2} + A_{1}A_{2} = \frac {1}{\sin ∠O} - H_{2}$$

假设内外三角形都是等腰直角三角形,则$\sin ∠O = \frac {\sqrt 2}{2}$,那么总偏移量$S$为

$$S = \sqrt 2 - H_{2}$$

将 demo 中的参数$H_{2} = 40$代入方程式,求得

$$S = \sqrt 2 - 40 \approx -39$$

所以横坐标偏移量为 -50px,纵坐标偏移量为 -49px

<style>
.arrow {
  position: relative;
  width: 0;
  height: 0;
  border-width: 0 50px 50px 50px;
  border-style: solid;
  border-color: transparent transparent #409eff transparent;
}
.arrow:before {
  content: '';
  display: block;
  position: absolute;
  top: -49px;
  left: -50px;
  border-width: 50px;
  border-style: solid;
  border-color: transparent transparent #fff transparent;
}
</style>
<div class="arrow"></div>

实现等腰直角箭头的另一种方式

等腰直角箭头 无非就是正方形的两条邻边,我们只需要将另外两条邻边透明化,同时按需求旋转图形指向就可以了。

这里继续利用伪元素实现。为了优化元素的可用性,我们使伪元素的宽高百分比于父元素,建立以下模型

那么,伪元素的宽度和父元素宽度的关系为

$$W_{伪} = \frac{\sqrt 2}{2}W \approx 0.7W$$

<style>
.arrow {
  position: relative;
  width: 50px;
  height: 50px;
}
.arrow:before {
  content: '';
  display: block;
  position: absolute;
  top: 0;
  left: 50%;
  width: 70%;
  height: 70%;
  border-width: 1px;
  border-style: solid;
  border-color: #409eff transparent transparent #409eff;
  transform: rotate(45deg);
  transform-origin: 0 0;
}
</style>
<div class="arrow"></div>

所有 demo 详见:https://codepen.io/JunreyCen/pen/LovdaM

Reference

PS: 文章首发于 简书 ,欢迎大家关注。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant