CSS 搞事技巧:border+transparent
介绍
出门忘带电源线,快递到了终于可以继续水文章了。好不容易获得一个面试机会,面试官很 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}`; } } }
效果图:
最后
期间步骤只是思路过程,详情请查看项目源码,调试过程中不可避免会进行一些修改。
面试前还是要为面试刷下题目的,不然真的容易懵……