<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 (2)
The string concatenation method makes me gag, but the object notation seems useful for abstracting a complex computed style. Didn't know that, thanks!