介绍
出门忘带电源线,快递到了终于可以继续水文章了。好不容易获得一个面试机会,面试官很 Nice,可惜的是当时处于懵逼状态,错过了大好的机会:
面试官:巴拉巴拉吧……
我:嗯,啊,这个,那(吱吱呜呜)……
面试官:你知道怎么绘制三角形嘛?
我:主要是利用了 border 和 transparent 这两个属性。其余边设置为 transparent,然后将对应的方向设置为需要的颜色即可,一般常用等边,等腰啊来装饰一下。
面试官:那你知道不等边三角形怎么写吗?
我:不就是那么写么(陷入懵逼状态),然后又迅速说用伪元素来模拟一下?
面试官:你分别设置下高度不就好了。
我:……
效果展示:
示例源码、在线示例
三角形原理
通过图形展示能够更明显显示出区别:
1. 简单的正方形
代码:
<div class=”square”></div>
$square-size = 100px
.square
width $square-size
height $square-size
border 5px solid
border-color #893615 #E76B56 #A72310 #0C1F22
效果图:
加强一下效果:
$square-size = 100px
$border-size = 60px
.square
width $square-size
height $square-size
border $border-size solid
border-color #893615 #E76B56 #A72310 #0C1F22
可以清晰的看到每个边都是一个梯形。
2. 检查正方形
打开控制台即可:
可以看到中间的空白即为我们设置的 100 * 100,这是由于我们的盒模型(box-sizing)为 content-box 导致的结果。
那我们将其设置为 border-box,查看其结果:
由 border-box 可知,由于两边 border 大小为 60,所以 60*2=120 > 100,内部的 width 即为 0。
3. 默认盒模型的正方形
在上方已经说明了,正方形的 size 被挤压为 0 时就会得到三角形的效果。
那么此处就在默认盒模型的情况下创建一个三角形:
$square-size = 0
$border-size = 60px
.square
width $square-size
height $square-size
border $border-size solid
border-color #893615 #E76B56 #A72310 #0C1F22
4. 隐藏不必的边
最后,生成三角形就水到渠成了(保留目标相反方向的颜色),举几个例子。
三角形开角向上:
$square-size = 0
$border-size = 60px
.triangle
width $square-size
height $square-size
border $border-size solid transparent
border-bottom-color #A72310
三角形开角向右:
$square-size = 0
$border-size = 60px
.triangle
width $square-size
height $square-size
border $border-size solid transparent
border-left-color #0C1F22
三角形开角向左上:
$square-size = 0
$border-size = 60px
.triangle
width $square-size
height $square-size
border $border-size solid transparent
border-left-color #0C1F22
border-top-color #893615
三角形生成器
每次还要想一想怎么写三角形很麻烦,将其可视化,每次只需要点一点就创建一个三角形才是极好的。
友情提示:以下涉及 Vue 相关概念
参考链接
0. 基本结构
<Layout-Layout
:background-color=”bgColor”
class=”generate-triangle”
>
<aside class=”settings”>
<section class=”settings_direction”>
<h4 class=”title”> 三角形方向 </h4>
</section>
<section class=”settings_type”>
<h4 class=”title”> 三角形类型 </h4>
</section>
<section class=”settings_color”>
<h4 class=”title”> 三角形颜色 </h4>
</section>
</aside>
<main class=”exhibition”>
<section class=”rendering”>
<h4> 效果图 </h4>
</section>
<section class=”code”>
<h4> 代码 </h4>
</section>
</main>
</Layout-Layout>
.generate-triangle
display flex
.title
margin 0
padding 0
.settings
flex-basis 30%
.exhibition
flex auto
background-color #cdd1d3 // 银鱼白
.settings
display flex
flex-direction column
padding-top 12px
.settings_direction,
.settings_type,
.settings_color
display flex
justify-content center
.settings_type,
.settings_color
flex-basis 20%
.settings_direction
flex auto
.exhibition
display flex
flex-direction column
padding-top 12px
.rendering,
.code
display flex
justify-content center
.code
flex-basis 35%
.rendering
flex auto
效果图:
1. 方向选择
在开始写一个三角形时,需要确定这个三角的朝向,如向上、向下、或向左上。这时候我们就需要一个点击的子组件来触发效果了:
<div class=”triangle-direction”>
<section
:class=”direction.name === ‘oblique’ ? ‘square-t45’ : ‘square'”
v-for=”(direction, index) in directions”
:key=”index”
>
<div
class=”single”
v-for=”(item, index) in direction.single”
:key=”index”
:class=”{active: direction.name + index === active}”
@click.stop=”changeDirection(item, direction.name + index)”
>
</div>
</section>
</div>
export default {
name: “triangle-direction”,
data: () => {
return {
active: “oblique0”,
directions: [
{
name: “oblique”,
single: [“top”, “right”, “bottom”, “left”]
},
{
name: “positive”,
single: [“top-left”, “top-right”, “bottom-right”, “bottom-left”]
}
]
};
},
mounted() {
this.changeDirection(“top”, “oblique0”);
},
methods: {
changeDirection(val, index) {
this.active = index;
this.$emit(“getDirection”, val);
}
}
};
效果图:
2. 类型选择
此处将三角形分为三种:等边三角形、等腰三角形、不等边三角形。
类型选择组件依赖于方向组件,需要验证传入的值,并且在不同的值会有不同的输出结果。在上文解释过,斜方向的三角形是由两个 border 组成,所以这种类型的将不提供等边的形式:
<div class=”triangle-type”>
<button
class=”type-button”
v-for=”(type, index) in triangleTypes”
v-show=”type.en !== ‘equilateral’ || equilateral”
:key=”index”
:class=”{active: index === active}”
@click.stop=”changeType(type.en, index)”
>{{type.zh}}</button>
</div>
export default {
name: “triangle-type”,
data: () => {
return {
active: 0,
equilateral: false,
triangleTypes: [
{
en: “equilateral”,
zh: “ 等边 ”
},
{
en: “isosceles”,
zh: “ 等腰 ”
},
{
en: “scalene”,
zh: “ 不等边 ”
}
]
};
},
props: {
type: {
type: String,
validator: function(val) {
return [
“top”,
“right”,
“left”,
“bottom”,
“top-left”,
“top-right”,
“bottom-left”,
“bottom-right”
].includes(val);
}
}
},
watch: {
type: {
handler: function(val) {
const isPositive = [“top”, “right”, “left”, “bottom”].includes(val);
this.equilateral = isPositive;
if (isPositive) {
this.changeType(‘equilateral’, 0);
} else {
this.changeType(‘isosceles’, 1);
}
},
immediate: true
}
},
methods: {
changeType(item, index) {
this.active = index;
this.$emit(“getType”, item);
}
}
};
效果图:
3. 颜色选取
现在 input 提供了 type=”color” 这一选项,制作一个颜色选择器还是很简单的,对于 input 可以使用之前提及的 CSS 搞事技巧:checkbox+label+selector 来隐藏它:
<div class=”color-picker”>
<label for=”color-picker”>
<span class=”color-name” :style=”{backgroundColor: color}”> {{color}} </span>
<input type=”color” v-model=”color” id=”color-picker” @change=”changeColor”>
</label>
</div>
export default {
name: ‘color-picker’,
data: () => {
return {
color: ‘#000000’
}
},
mounted() {
this.changeColor();
},
methods: {
changeColor() {
this.$emit(‘getColor’, this.color);
}
}
}
效果图:
4. 初步效果
效果图来依赖于三个数据:方向、类型及颜色。依次适配这三个即可。
首先完成,方向及颜色问题,先初步看一下效果图:
5. 宽高选取
在原理中说明了,三角形实际上是一个矩形隐藏了其余 border 形成的。以方向等边三角形为例子:若需要边长度为 50px 的的三角形,则根据勾股定理可得出:border-width: 0 28.87px 50px;
<div class=”triangle-width”>
<div class=”width-inputs”>
<input
v-model=”bottom”
class=”width-input”
type=”number”
min=”0″
max=”180″
placeholder=” 底 ”
:disabled=”!isPositive”
@change=”getBorder”
>
<input
v-model=”sideOne”
class=”width-input”
type=”number”
min=”0″
max=”180″
placeholder=” 边 ”
:disabled=”type !== ‘isosceles’ && type !== ‘scalene'”
@change=”getBorder”
>
<input
v-model=”sideTwo”
class=”width-input”
type=”number”
min=”0″
max=”180″
placeholder=” 侧边 ”
:disabled=”type !== ‘scalene'”
@change=”getBorder”
>
</div>
</div>
export default {
name: “triangle-width”,
props: {
type: {
type: String,
validator: function(val) {
return [“equilateral”, “isosceles”, “scalene”].includes(val);
}
},
direction: {
type: String,
validator: function(val) {
return [
“top”,
“right”,
“left”,
“bottom”,
“top-left”,
“top-right”,
“bottom-left”,
“bottom-right”
].includes(val);
}
}
},
data: () => {
return {
bottom: 50,
sideOne: 50,
sideTwo: 50,
borderWidth: ”,
isPositive: false
};
},
watch: {
direction: {
handler: function(val) {
this.isPositive = [“top”, “right”, “left”, “bottom”].includes(val)
this.getBorder();
},
immediate: true
},
type: {
handler: function() {
this.getBorder();
}
}
},
methods: {
getBorder() {
let direction = this.direction;
let type = this.type;
switch(type) {
case ‘equilateral’:
this.calcEquBorder(direction);
break;
case ‘isosceles’:
this.calcIsoBorder(direction);
break;
case ‘scalene’:
this.calcScaBorder(direction);
break;
default:
break;
}
this.$emit(‘getBorderWidth’, this.borderWidth);
},
calcEquBorder(direction) {
let bottom = this.bottom;
let height = (bottom / Math.sqrt(3)).toFixed(2);
switch(direction) {
case ‘top’:
this.borderWidth = `0 ${height}px ${bottom}px`;
break;
case ‘right’:
this.borderWidth = `${height}px 0 ${height}px ${bottom}px`;
break;
case ‘bottom’:
this.borderWidth = `${bottom}px ${height}px 0`;
break;
case ‘left’:
this.borderWidth = `${height}px ${bottom}px ${height}px 0`;
break;
default:
break;
}
},
}
};
效果图:
6. 生成代码
终于到了最后一步了,生成代码有很多方式,可以将之前从子组件传递出来的数据处理下输出。这里选择一种较为取巧的形式,因为这边使用的是行内 style 样式,所以可以直接在它的 DOM 上获取。
<div class=”triangle” ref=”triangleRendering” :style=”[borderStyle, { borderWidth: borderWidth}]”></div>
export default {
methods: {
postCode() {
this.$nextTick(() => {
let dom = this.$refs.triangleRendering;
let code = dom.attributes.style.textContent;
this.$emit(‘getCode’, code);
})
}
}
}
export default {
name: ‘triangle-code’,
props: {
code: {
type: String,
required: true
}
},
watch: {
code: {
handler: function(code) {
this.handleCode(code);
},
immediate: true
}
},
data: () => {
return {
copyCode: ”
}
},
methods: {
handleCode(code) {
code = code.replace(/\;/g,”;\n”);
this.copyCode = `width: 0;\n height: 0;\n border: solid transparent;\n ${code}`;
}
}
}
效果图:
最后
期间步骤只是思路过程,详情请查看项目源码,调试过程中不可避免会进行一些修改。
面试前还是要为面试刷下题目的,不然真的容易懵……