DEV Community

The Jared Wilcurt
The Jared Wilcurt

Posted on • Updated on

Basic approaches to styling in Vue

<template>
  <div>

    <div
      :style="{
        'border': editMode ? '1px dashed #000' : '',
        'border-color': showGear ? '#333' : '#DEDEDE',
        'border-bottom-left-radius': hasStub ? '0px' : '',
        'border-bottom-right-radius': hasStub ? '0px' : '',
        'border-bottom': hasStub ? '0px' : '',
        'border-top-left-radius': hasParent ? '0px' : '',
        'border-top-right-radius': hasParent ? '0px' : '',
        'border-top': hasParent ? '0px' : ''
      }"
    >
      In template approach.
      It's kinda gnarly though, that's a lot of ternaries.
      And ternaries are the enemy of readability.
      Let's move this complexity down to a Computed instead.
      We'll try two approaches, string concatenation, and
      Vue's object syntax.
    </div>

    <div :style="stringConcatApproach">
      See JS for details.
    </div>

    <div :style="objectApproach">
      See JS for details.
    </div>

    <div
      :class="{
        editModeBorder: editMode,
        showGearBorder: showGear,
        hideGearBorder: !showGear,
        hasStubBorder: hasStub,
        hasParentBorder: hasParent
      }"
    >
      This is much cleaner. It simplifies the logic and doesn't really even need to
      be a computed at this point. It does require you to create classes to contain
      each of these styles. But as you'll read below, that is well worth the effort
      to avoid the negatives of inline-styles.
    </div>

    <div :class="borderStyles">
      <p>
        But if this logic is used by multiple compontents/elements, then making it
        a computed means the class object will only need to be produced once and
        all elements using it will get a cached copy (fast!).
      </p>
      <p>
        Using classes on their own though is the real performance boost. Browser
        rendering engines are specially optimized for classes. When a browser sees
        1,000 divs with the same class, it will actually do the layout and painting
        in parallel. However, if those 1,000 divs all had identical inline styles,
        each div must be painted sequentially and treated as a unique snowflake
        (1000x slower). If you've heard that "updating the DOM is slow", well
        browser paints are an order of magnitude slower. Vue does a good job of
        avoiding unnecesary DOM updates, but it's up to you to always use classes
        and only use inline-styles when absolutely needed.
      </p>
      <p>
        Because inline-styles are a huge performance hit, they should only be used
        when you have truly dynamic styling that cannot be predicted. Such as a
        color picker where the user could pick any color. But in this example,
        everything is predictable, so it should be classes.
      </p>
    </div>

    <div>
      One last note. If you are using atomic CSS classes, then using the string
      concatenation approach (but for classes, instead of inline-styles), tends
      to be the best approach for readability. Using string concatenation on
      inline-styles looks much worse than it does on classes.
    </div>

  </div>
</template>

<script>
export default {
  name: 'StyleExamples',
  data: function () {
    return {
      editMode: false,
      showGear: false,
      hasStub: false,
      hasParent: false
    };
  },
  computed: {
    // First we'll try creating vanilla HTML string from scratch.
    // :style="'border: 1px dashed #000;border-color: #333'"
    stringConcatApproach: function () {
      let border = '';
      let borderColor = 'border-color: #DEDEDE';
      let borderBottomLeftRadius = '';
      let borderBottomRightRadius = '';
      let borderBottom = '';
      let borderTopLeftRadius = '';
      let borderTopRightRadius = '';
      let borderTop = '';

      if (this.editMode) {
        border = 'border: 1px dashed #000';
      }
      if (this.showGear) {
        borderColor = 'border-color: #333';
      }
      if (this.hasStub) {
        borderBottomLeftRadius = 'border-bottom-left-radius: 0px';
        borderBottomRightRadius = 'border-bottom-right-radius: 0px';
        borderBottom = 'border-bottom: 0px';
      }
      if (this.hasParent) {
        borderTopLeftRadius = 'border-top-left-radius: 0px';
        borderTopRightRadius = 'border-top-right-radius: 0px';
        borderTop = 'border-top: 0px';
      }

      return [
        border,
        borderColor,
        borderBottomLeftRadius,
        borderBottomRightRadius,
        borderBottom,
        borderTopLeftRadius,
        borderTopRightRadius,
        borderTop
      ].filter(Boolean).join(';');
    },
    // Next let's try using Vue's built in Object syntax for inline-styles
    // This is nice because it ignores keys with falsy values for free.
    objectApproach: function () {
      let styles = {
        'border': '',
        'border-color': '#DEDEDE',
        'border-bottom-left-radius': '',
        'border-bottom-right-radius': '',
        'border-bottom': '',
        'border-top-left-radius': '',
        'border-top-right-radius': '',
        'border-top': ''
      };

      if (this.editMode) {
        styles['border'] = '1px dashed #000';
      }
      if (this.showGear) {
        styles['border-color'] = '#333';
      }
      if (this.hasStub) {
        styles['border-bottom-left-radius'] = '0px';
        styles['border-bottom-right-radius'] = '0px';
        styles['border-bottom'] = '0px';
      }
      if (this.hasParent) {
        styles['border-top-left-radius'] = '0px';
        styles['border-top-right-radius'] = '0px';
        styles['border-top'] = '0px';
      }

      return styles;
    },
    // See the template for more info on this approach
    borderStyles: function () {
      return {
        editModeBorder: this.editMode,
        showGearBorder: this.showGear,
        hideGearBorder: !this.showGear,
        hasStubBorder: this.hasStub,
        hasParentBorder: this.hasParent
      };
    }
  }
};
</script>

<style lang="sass">

/* While we're at it, creating classes, let's use some Sass variables and a mixin! */

$black: #000
$gray33: #333
$grayDE: #DEDEDE

=zeroOutBorderSide ($side)
    border-#{$side}-left-radius: 0px
    border-#{$side}-right-radius: 0px
    border-#{$side}: 0px

.editModeBorder
    border: 1px dashed $black

.showGearBorder
    border-color: $gray33

.hideGearBorder
    border-color: $grayDE

.hasStubBorder
    +zeroOutBorderSide(bottom)

.hasParentBorder
    +zeroOutBorderSide(top)

</style>

Wait, did you just write an entire article as a functioning Vue Single File Component? oh yeah, I guess I did.

Top comments (1)

Collapse
 
dyllandry profile image
Dylan Landry

The string concatenation method makes me gag, but the object notation seems useful for abstracting a complex computed style. Didn't know that, thanks!