1

I have to create a button with the shape of a triangle in each side.

My approach was to create a div that contains 4 more divs (one per corner) so I can shape the triangles by overlapping divs, using linear-gradient and white color for the background.

The problem is that I also need to edit a border for the shape created, and it is not possible because every div has its own border.

Any way to obtain borders just for the shape of the final button?

.button {
  height: 50px;
  width: 140px;
  position: relative;
  background: darkred;
}

.button:hover {
  background: black;
}

.text {
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
  color: white;
  font-size: 15px;
  font-family: Tahoma;
}

.text:hover {
  color: red;
}

.left,
.right {
  width: 8%;
  height: 50%;
  position: absolute;
}

.left {
  right: 0;
}

.right {
  left: 0;
}

.down {
  bottom: 0;
}

.up {
  top: 0;
}

.left.up {
  background: linear-gradient(225deg, white 87%, white 87%, transparent 0%);
}

.left.down {
  background: linear-gradient(-45deg, white 87%, white 87%, transparent 0%);
}

.right.down {
  background: linear-gradient(45deg, white 87%, white 87%, transparent 0%);
}

.right.up {
  background: linear-gradient(135deg, white 87%, white 87%, transparent 0%);
}
<div class="button text">
  <span>Discover More</span>
  <div class="left up"></div>
  <div class="left down"></div>
  <div class="right up"></div>
  <div class="right down"></div>
</div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415

2 Answers2

0

Here is an idea with few code and CSS variables to easily adjust the shape:

.box {
  --c:green; /* color */
  --a:30deg; /* angle */
  --b:3px;   /* border thickness */
  --w:20px;  /* width of of arrow */

  padding:30px calc(var(--w) + 20px);
  text-align:center;
  margin:10px;
  background:
    linear-gradient(var(--c) 0 0) top,
    linear-gradient(var(--c) 0 0) bottom;
  background-size:calc(100% - 2*var(--w)) var(--b);
  background-repeat:no-repeat;
  overflow:hidden;
  display:inline-block;
  position:relative;
}
.box::before,
.box::after,
.box span::before,
.box span::after{
  content:"";
  position:absolute;
  box-sizing:border-box;
  left:0;
  top:0;
  height:50%;
  width:var(--w);
  border-right:var(--b) solid var(--c);
  border-bottom:var(--b) solid var(--c);
  transform-origin:0 calc(100% - var(--b)/2);
  transform:scaleY(var(--s,1)) skewY(calc(-1*var(--a)));
}
.box::after,
.box span::after{
  --s:-1;
}

.box span::before,
.box span::after {
  left:auto;
  right:0;
  border-right:0;
  border-left:var(--b) solid var(--c);
  transform-origin:100% calc(100% - var(--b)/2);
  transform:scaleY(var(--s,1)) skewY(var(--a));
}
<div class="box"><span></span> some text here </div>
<div class="box" style="--c:red;--b:5px;--a:45deg"><span></span> some text here </div>
<div class="box" style="--c:blue;--b:2px;--a:25deg"><span></span> some text<br> here </div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • Your solution is amazing. Works perfectly well. Thank you very much! – Abel Moreno Jul 20 '20 at 12:39
  • @AbelMoreno thanks, don't forget to accept the answer if it solves your issue – Temani Afif Jul 20 '20 at 12:40
  • The only issue is the compatibility with Internet Explorer, but I guess it is impossible to adapt it to that browser. – Abel Moreno Jul 20 '20 at 15:00
  • @AbelMoreno for IE remove the CSS variables and put the value directly inside each property. You won't have flexibility but better support – Temani Afif Jul 20 '20 at 15:09
  • I have tried that but still IE does not support certain features. I will have to create a SVG. – Abel Moreno Jul 21 '20 at 08:28
  • @AbelMoreno IE Support all of them, they simply need adjustement. fir example this : `linear-gradient(green 0 0)` need to be written `linear-gradient(green,green)` and you have to remove all the calc() too and replace them with the final value – Temani Afif Jul 21 '20 at 08:31
0

The final solution to attend full responsiveness was to create a SVG image as a Vue.js component, where size and shape (called "path" in a SVG) depend on the text inserted.

<script>
export default {
  data() {
    return {
      width: 150,
      height: 8,
      triangle: 12
    };
  },
  props: {
    text: {
      type: String,
      required: true
    },
    left: {
      type: Boolean,
      default: false
    },
    right: {
      type: Boolean,
      default: false
    },
    icon: {
      type: String,
      default: null
    }
  },
  computed: {
    rightTriangle() {
      const t = this.triangle;
      const h = this.height;

      return this.right
        ? `l 0 ${h} l ${t} ${t} l -${t} ${t} l 0 ${h}`
        : `l 0 ${h} l 0 ${t} l 0 ${t} l 0 ${h}`;
    },
    leftTriangle() {
      const t = this.triangle;
      const h = this.height;

      return this.left
        ? `l 0 -${h} l -${t} -${t} l ${t} -${t} l 0 -${h}`
        : `l 0 -${h} l 0 -${t} l 0 -${t} l 0 -${h}`;
    },
    svgPath() {
      return `m ${this.svgMargin.x} ${this.svgMargin.y} l ${this.width} 0 ${
        this.rightTriangle
      } l -${this.width} 0 ${this.leftTriangle}`;
    },
    svgHeight() {
      return this.height * 2 + this.triangle * 2 + 4;
    },
    svgWidth() {
      //https://stackoverflow.com/a/20916980
      //distance between [0,0] to [x, y]
      var triangleWidth = Math.sqrt(this.triangle * this.triangle);
      var numTriangles = 2;

      return this.width + triangleWidth * numTriangles + 2;
    },
    svgMargin() {
      return {
        x: this.triangle + 1,
        y: 2
      };
    }
  },
  watch: {
    text: {
      handler() {
        this.$nextTick(() => {
          this.updateButtonWidth();
        });
      },
      immediate: true
    }
  },
  methods: {
    updateButtonWidth() {
      const iconSize = this.icon ? 40 : 0;

      this.width =
        document.querySelector(".atc-button-text").getBBox().width +
        40 +
        iconSize;
    },
    handleClickEvent() {
      this.$emit("click-event");
    }
  }
};
</script>
<style scoped>
.atc-button-wrapper {
  display: flex;
  justify-content: center;
  position: relative;
}
.atc-button-wrapper.push-left {
  justify-content: flex-start;
}
.atc-button-wrapper.push-right {
  justify-content: flex-end;
}
.svg-content {
  position: relative;
}
.atc-button-content:hover {
  cursor: pointer;
}
.atc-button-content path {
  fill: transparent;
  stroke: #ccba83;
}
.atc-button-content:hover path {
  fill: #ccba83;
  stroke: #ccba83;
}
.atc-button-content text {
  fill: #ccba83;
  font-family: cursive;
  font-weight: bold;
}
.atc-button-content:hover text {
  fill: white;
}

.atc-button-icon-wrapper {
  position: absolute;
  display: flex;
  align-items: center;
  margin: auto;
  top: -4px;
  left: 30px;
  bottom: 0;
}
.atc-button-icon {
  font-size: 20pt;
  color: #ccba83;
}
.atc-button-icon:hover {
  cursor: pointer;
  color: white;
}

.atc-button-content:hover ~ .atc-button-icon-wrapper .atc-button-icon {
  color: white;
}

.atc-button-icon-wrapper:hover ~ .atc-button .atc-button-content path {
  fill: #ccba83 !important;
  stroke: #ccba83 !important;
}
</style>
<template>
  <div class="atc-button-wrapper">
    <div class="svg-content">
      <svg class="atc-button" :height="svgHeight" :width="svgWidth">
        <g @click="handleClickEvent()" class="atc-button-content">
          <path stroke="red" :d="svgPath"></path>
          <text
            class="atc-button-text"
            :x="icon ? '56%' : '50%'"
            y="52%"
            dominant-baseline="middle"
            text-anchor="middle"
          >{{text}}</text>
        </g>
      </svg>

      <div class="atc-button-icon-wrapper">
        <fa-icon :icon="icon" class="atc-button-icon"></fa-icon>
      </div>
    </div>
  </div>
</template>