Experimenting with creating a beveled corner button


Experimenting with creating a beveled corner button

Dec 08 2015 | by BALAZS TOTH


In this blog post we’re going to create a beveled corner button. This will be a transparent frame with some text in it. The length of the text is unknown, and the border and the text need to be available in a couple of colours. First, let’s do a quick search for CSS solutions to see how others would create something like this:

There are some articles and forum topics about beveled corners, for example a beveled corners div using border-radius and css gradients, but with these we can’t create just the frame for our button, it needs to be filled with colour (like jQuery Corner). There are some more results found using simple borders, multiple backgrounds, negative border-radius, border-corner-shape as a way of creating beveled corners, but these solutions still don’t really fit with our requirements…

So, let’s see what we can do with images

If we want our button to stretch when more text is in it, without distorting of the beveled corner, we can use a maximum-sized, “half” image as a background image, combined with CSS borders. To fix the beveled corner, we need the background image to be positioned at the bottom and right, and we need to add the top and left borders in CSS. There are many possible image formats, but the right format for this could be PNG or SVG. Let’s see both in practice and compare their advantages and disadvantages.

PNG format option

First of all, we create the transparent PNGs with the bottom and right side of the button with the beveled corner. Let’s say that the size of our images (and the maximum size of our button) will be 400x200px.

(3.38KB RGBA PNG, optimised to 222B Indexed RGB PNG)

(3.33KB RGBA PNG, optimised to 224B Indexed RGB PNG)

HTML

<div class="bevbutton orange">
  Lorem ipsum
</div>

CSS

.bevbutton {
  background-position: bottom right;
  background-repeat: no-repeat;
  border-top: 2px solid;
  border-left: 2px solid;
  box-sizing: border-box;
  cursor: pointer;
  display: inline-block;
  font-family: "Arial Bold", sans-serif;
  font-size: 1.125em;
  max-height: 200px;
  max-width: 400px;
  padding: .65em 2.4em .6em 2.4em;
  text-align: center;
}
.bevbutton.blue {
  background-image: url(../images/bevbutton_blue.png);
  border-color: blue;
  color: blue;
}
.bevbutton.blue:hover {
  background-image: url(../images/bevbutton_blue_on.png);
  color: white;
}

The only thing we need to do is to create the rest of the images in different colours, and add these into our stylesheet. Just to do some quick math, we have 365 bytes of a base CSS, and 446 bytes of images, plus 211 bytes of extra CSS per colour. Which is around 3650 bytes all together in the case of having 5 colour options.

If we open it in a browser, we can see that we’ve successfully created our button. 

But on the first page load, the images aren’t cached, and when we move our mouse over the button, we will see only the border while the hover state image will be loading. Our images are optimized and have as small a file size as possible, but the extra image always needs an extra HTTP request, and results in a loading delay. To solve this, we can encode our images to base64 code, and add into our CSS. In this case, all images will be loaded with the stylesheet together, which means no loading time on mouse over event. (But means extra loading time for CSS, around 300 bytes per image. All together it is 4445 bytes of CSS with 5 colours.)

background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAADIAgMAAAC7e19xAAAACVBMVEVMaXH+UAD+UAAsFM3wAAAAAnRSTlMAI9T0vEoAAACCSURBVHhe7dFBDYAwFAVBuPSAhKr5eqoGEb20KgkieAnJrIE57BHoSiIQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCA9gYyAca4A0mZiyZ1YUn9fYokl++sqgLxLHhgXkCCDAIojAAAAAElFTkSuQmCC')

As I mentioned at the beginning of this blog post, we need the button to be available in several colours. But one thing we have yet to consider is this: what if we want to change the colours in the future? If that’s the case, we have to recolour our images one by one, optimize, and encode all of our images, and insert them into the style sheet. This means 10 images if we have 5 different colours, or even 20 images if there are 10 colours.

SVG format option

Let’s test the SVG file format, and see if we can do anything better with it. SVG has great browser support, and as it is a vector graphic format it never will be blurry. We can set CSS rules for the SVG images, set colours for example, as long as we use it as a normal image. If we use it as a background image, we lose all of the control to style our SVG image. But in this case we have to use it as a background image, and we want to style it as well. So what can we do?

First, create the whole button in SVG

If we have a look at the code of the SVG files, we can see the attributes of the path. The fill and fill-opacity attribute make the difference between the images. From the PNG example we know that in order to keep it resizable, we need to use just the half of the frame of the button, and we should use it as an infile parameter in the style sheet. 

Note that, if we create our svg image as a line object, we won’t be able to use the fill-opacity attribute. To make our image to seem to be a line, it is enough to place the left and the top corners outside of the viewbox. 1 pixel for the left corners left, 1 pixel for the top corners up, so the top left corner will be moved both ways. 

<svg height="200" viewbox="0 0 400 200" width="400" xmlns="http://www.w3.org/2000/svg">
path d="M-1,-1 L399,-1 L399,185.5 L385.5,199 L-1,199 L-1,-1 Z" fill-opacity="0" stroke="#fe5000" stroke-width="2px"/>
</svg>

(Note: this is optimised code)

A couple of browsers support SVG background images with a data URI, UTF-8 encoded. This would be much easier for us to use, because we can copy and paste the SVG code with the data URI scheme into the CSS. If we want our button to be cross-browser compatible, we have to url-encode the SVG code, and now we've reached that level like at the last example with PNG images.

background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22450%22%20height%3D%2248%22%20viewBox%3D%220%200%20450%2048%22%3E%3Cpath%20stroke%3D%22%23ba102c%22%20stroke-width%3D%222px%22%20fill%3D%22%23ba102c%22%20fill-opacity%3D%221%22%20d%3D%22M-1%2C-1%20L449%2C-1%20L449%2C33.5%20L435.5%2C47%20L-1%2C47%20L-1%2C-1%20Z%22%2F%3E%3C%2Fsvg%3E")

As we can see, after the encoding, it is not really easy for a human to read. So we are not really closer to the fully editable SVG button.

And here comes the power of Sass, with variables for maintainability, with mixins for reusability and with some really handy built-in functions... Unfortunately, Sass doesn't have any built-in functions for url-encode, but we can find code on the web , which will take care of the hard part of the job.

This code creates an US-ASCII encoded svg, but if we remove space, single quote, semi-colon, equal, comma and slash from the map, then we will optimize, and make the result much easier to read. And also change the charset from US-ASCII to UTF-8 .

The inline-SVG function has a string input variable. This string will be the code of our SVG image. Let's create two variables, and make the SVG code two parts. One for the opening SVG tag, and one for the coordinates. It is important to change all double quotes to single quotes, we don't need that to be encoded.

Then, let's create two functions for the normal and the hover images. Both need one input variable, this will be used as stroke colour, and stroke and fill colours for the second one. The return value of the functions needs to be the inline-SVG function, and between the brackets, we insert all of the variables and missing parts to build up the original SVG code. The only difference between the two functions are the fill attribute, and the value of the fill-opacity attribute. (0 or 1)

$svgBevTag: "<svg xmlns='http://www.w3.org/2000/svg' width='400' height='200' viewBox='0 0 400 200'>";
$svgBevPath: "d='M-1,-1 L399,-1 L399,185.5 L385.5,199 L-1,199 L-1,-1 Z'";

@function svgbevbutton($svgBev-color) {
  @return inline-svg($svgBevTag + "<path stroke='" + $svgBev-color + "' stroke-width='2px' fill-opacity='0' " + $svgBevPath + "/></svg>");
}

@function svgbevbuttonOn($svgBev-color) {
  @return inline-svg($svgBevTag + "<path stroke='" + $svgBev-color + "' stroke-width='2px' fill='" + $svgBev-color + "' fill-opacity='1' " + $svgBevPath + "/></svg>");
}

We need two mixins for the normal and the hover buttons. The first one with one input variable, the text, the border and the SVG's stroke will have the same colour, for the second one, we need two, the text colour will be different.

@mixin bevbutton($color) {
  background-image: svgbevbutton($color);
  border-top: 2px solid $color;
  border-left: 2px solid $color;
  color: $color;
}
@mixin bevbuttonOn($color, $textcolor) {
  background-image: svgbevbuttonOn($color);
  color: $textcolor;
}

Then, all we have to do is set our colour variables, and include our newly created mixins with the different colours.

.bevbutton {
  background: {
    position: bottom right;
    repeat: no-repeat;
  }
  border: {
    top: 2px solid;
    left: 2px solid;
  }
  box-sizing: border-box;
  cursor: pointer;
  display: inline-block;
  font: {
    family: "Arial Bold", sans-serif;
    size: 1.125em;
  }
  max-height: 200px;
  max-width: 400px;
  padding: .65em 2.4em .6em 2.4em;
  text-align: center;
  &.blue {
    @include bevbutton($color-blue);
  }
    &.blue:hover {
      @include bevbuttonOn($color-blue, $color-white);
    }
  &.green {
    @include bevbutton($color-green);
  }
    &.green:hover{
      @include bevbuttonOn($color-green, $color-white);
    }
  &.orange{
    @include bevbutton($color-orange);
  }
    &.orange:hover{
      @include bevbuttonOn($color-orange, $color-white);
    }
  &.red {
    @include bevbutton($color-red);
  }
    &.red:hover{
      @include bevbuttonOn($color-red, $color-white);
    }
  &.white{
    @include bevbutton($color-white);
  }
    &.white:hover{
      @include bevbuttonOn($color-white, $color-red);
    }
}

The result is 3930 bytes of CSS with 5 colours. (It would be 5080 bytes without optimized data URI.) This is just a bit bigger than if we would have used optimized png images, but we don't need any extra HTTP request. In addition, we have the opportunity to restyle our button any time. 

The result?

We can easily edit the colour, the size, or even the shape of our button. The limitation of this method is that, we can just change 1 corner, the other 3 have to be at their places (except if it has a fixed size), but the result will be a fast, cross-browser compatible asymmetric object from CSS. Sass will do all of the dirty work for us, and makes maintaining more simple.

Thank you for reading, I hope it was helpful.


Balazs Toth

Experimenting with creating a beveled corner button

BY BALAZS TOTH