CSS 搞事技巧:border+transparent

2019-04-15 admin

介绍

出门忘带电源线,快递到了终于可以继续水文章了。好不容易获得一个面试机会,面试官很 Nice,可惜的是当时处于懵逼状态,错过了大好的机会:

面试官:巴拉巴拉吧……

我:嗯,啊,这个,那(吱吱呜呜)……

面试官:你知道怎么绘制三角形嘛?

我:主要是利用了 bordertransparent 这两个属性。其余边设置为 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. 隐藏不必的边

最后,生成三角形就水到渠成了(保留目标相反方向的颜色),举几个例子。

  1. 三角形开角向上:

    $square-size = 0
    $border-size = 60px
    
    .triangle
      width $square-size
      height $square-size
      border $border-size solid transparent
      border-bottom-color #A72310
    

  1. 三角形开角向右:

    $square-size = 0
    $border-size = 60px
    
    .triangle
      width $square-size
      height $square-size
      border $border-size solid transparent
      border-left-color #0C1F22
    

  1. 三角形开角向左上:

    $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}`;
    }
  }
}

效果图:

最后

期间步骤只是思路过程,详情请查看项目源码,调试过程中不可避免会进行一些修改。

面试前还是要为面试刷下题目的,不然真的容易懵……

[转载]原文链接:https://segmentfault.com/a/1190000018869755

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-61822.html

文章标题:CSS 搞事技巧:border+transparent

相关文章
从 Node.js 分裂出 Io.js 事件看开源软件谁做主
Node.js 作为服务器编程语言的后起之秀,常用来构建和运行 Web 应用,近日却爆出其社区出现分裂。由于对官方运营商 Joyent 公司在 Node.js 管理上的长期不满,多位核心开发者另立门户,创建了分支 Io.js。从 GitHu...
2015-11-12
三步搞定vue在vscode的环境配置问题
1. vscode基础开发插件 vscode-icons 图标美化 Debugger for Chrome 调试 Beautify 代码格式化 Prettier 代码格式化 ESLint 代码规范 JavaScript (ES6) cod...
2017-12-25
jQuery的一些技巧大放送
1、关于页面元素的引用 通过jquery的$()引用元素包括通过id、class、元素名以及元素的层级关系及dom或者xpath条件等方法,且返回的对象为jquery对象(集合对象),不能直接调用dom定义的方法。 2、jQuery对象与d...
2015-11-11
关于Ajax应用开发需要注意的事项
接触Ajax,那时候的Ajax支持还不是很好,都要涉及底层,没有现成的框架给你调用。现在把常见的问题列举如下。 [b]1、编码问题[/b] 注意AJAX要取的文件是UTF-8编码的。GB2312编码传回BROWSE后中文会乱码。如果用VBS...
2015-11-11
面试官:谈谈你对 CSS 盒模型的认识?(你确定会?)
题目:谈谈你对 CSS 盒模型的认识 涉及知识点(层层递进): 基本概念:标准模型+ IE模型(区别) CSS如何设置这两种模型 JS如何设置获取盒子模型对应的宽和高 实例题(根据盒模型解释边距重叠) BFC(边距重叠解决方案) 1...
2018-06-09
Zepto Ajax 事件
当global: true时。在Ajax请求生命周期内,以下这些事件将被触发。 ajaxStart (global):如果没有其他Ajax请求当前活跃将会被触发。 ajaxBeforeSend (data: xhr, options):再...
2017-04-26
纯css实现窗户玻璃雨滴逼真效果
这里仅是用CSS技术来演示这样的一个场景,可能并不太实用。然而这是一个探索CSS新功能的最佳机会。可以让你尝试使用一些新特性和新工具。并且逐渐将在工作中实践。在制作窗口雨滴效果,将使用到HAML和Sass。 案例效果 看到上面的效果是不是...
2017-03-29
7个你可能不认识的CSS单位
浼楁墍鍛ㄧ煡CSS鎶€鏈�鎴戜滑铏界劧寰堢啛鎮夛紝鍦ㄤ娇鐢ㄧ殑杩囩▼鍗村緢瀹规槗琚�鍥颁綇锛岃繖璁╂垜浠�鍦ㄦ柊闂�棰樺嚭鐜扮殑鏃跺€欏彉寰楀緢涓嶅埄銆� 闅忕潃web缁х画涓嶆柇鍦板彂灞曪紝瀵逛簬鏂版妧鏈�鏂拌В鍐虫柟妗堢殑瑕佹眰涔熶細涓...
2015-11-11
详解使用vue-router进行页面切换时滚动条位置与滚动监听事件
按照正常的产品逻辑,我们在进行页面切换时滚动条应该是在页面顶部的,可是。。。在使用vue-router进行页面切换时,发现滚动条所处的位置被自动记录了下来,且在另一个组件内定义的滚动监听事件仍会运行,着实吃了一大惊。。。 说说我的破解方法:...
2017-03-13
回到顶部