doctype html

// Point of entry for Vue
#app
  rough-icon(icon='thumbs-up' prefix='fal' :stroke='blue' :fill='blue' roughness=0.6)
  rough-icon(icon='money-bill' prefix='far' :stroke='green' :fill='green')
  rough-icon(icon='quidditch' :stroke='brown300' :fill='brown400' roughness=1.5)
  rough-icon(icon='magnet' stroke='white' :fill='red' roughness=1.5)
  rough-icon(icon='coffee' stroke='white' fill='white' roughness=1.5)
  rough-icon(icon='heart' :stroke='red' :fill='red' roughness=2 bowing=4)
  rough-icon(icon='dolly' stroke='white' :fill='brown')
  rough-icon(icon='chess-rook' stroke='white' fill='white')
  rough-icon(icon='gamepad' stroke='white' :fill='grey')
  rough-icon(icon='lightbulb' stroke='white' :fill='yellow')
  rough-icon(icon='eye' prefix='far' :stroke='blue300' :fill='blue800' roughness=1.2)
  rough-icon(icon='snowflake' :stroke='lightBlue100' :fill='lightBlue100')
  rough-icon(icon='futbol' stroke='white' fill='white' roughness=1.3)
  rough-icon(icon='football-ball' stroke='white' fill='#795548')
  rough-icon(icon='flask' stroke='white' fill='#BA68C8' roughness=1.3)
  rough-icon(icon='battery-full' prefix='far' :stroke='green' :fill='green' roughness=0.9)
  rough-icon(icon='browser' stroke='white' fill='white')
  rough-icon(icon='sliders-h-square' prefix='far' stroke='white' fill='white')
  rough-icon(icon='signal' stroke='white' :fill='green')
  rough-icon(icon='trophy' stroke='white' :fill='yellow')
  rough-icon(icon='watch' stroke='white' fill='white')
  rough-icon(icon='pencil' stroke='white' fill='white')
  rough-icon(icon='vuejs' prefix='fab' :stroke='green' :fill='green')

// Component Templates
template#roughIcon
  canvas(ref='canvas')

// Profile link styling for self-shilling
a.shill(href='https://codepen.io/milesmanners' target='_blank' title='Made by Miles Manners')
View Compiled
@include theme($bg-dark-blue, $blue);

body {
  display: flex;
}

#app {
  margin: auto;
  display: flex;
  flex-flow: row wrap;
  align-items: center;
  justify-content: space-around;
}

canvas {
  display: block;
  margin: 10px;
}
/* jshint esversion: 6, asi: true, boss: true */

// Component Definitions
Vue.component('rough-icon', {
  template: '#roughIcon',
  props: {
    icon: {
      type: String,
      default: 'unknown'
    },
    prefix: {
      type: String,
      default: 'fas'
    },
    buffer: {
      type: Number,
      default: 30
    },
    scale: {
      type: Number,
      default: 0.15
    },
    roughness: {
      type: Number,
      default: 1
    },
    bowing: {
      type: Number,
      default: 1
    },
    stroke: {
      type: String,
      default: 'white'
    },
    strokeWidth: {
      type: Number,
      default: 1
    },
    fill: {
      type: String,
      default: 'white'
    },
    fillStyle: {
      type: String,
      default: 'hachure'
    },
    fillWeight: {
      type: Number,
      default: -1
    },
    hachureAngle: {
      type: Number,
      default: -41
    },
    hachureGap: {
      type: Number,
      default: -1
    },
    curveStepCount: {
      type: Number,
      default: 9
    },
    simplification: {
      type: Number,
      default: 0
    }
  },
  data () {
    return {
      rc: null,
      ctx: null,
      drawable: null
    }
  },
  computed: {
    options () {
      return {
        roughness: this.roughness,
        bowing: this.bowing,
        stroke: this.stroke,
        strokeWidth: this.strokeWidth,
        fill: this.fill,
        fillStyle: this.fillStyle,
        fillWeight: this.fillWeight,
        hachureAngle: this.hachureAngle,
        hachureGap: this.hachureGap,
        curveStepCount: this.curveStepCount,
        simplification: this.simplification
      }
    },
    path () {
      let iconDef = window.FontAwesome.findIconDefinition({ prefix: this.prefix, iconName: this.icon })
      let icon = window.FontAwesome.icon(iconDef)
      let path = icon.abstract[0].children[0].attributes.d
      let svgPath = new SvgPath(path).abs()
      
      let left = Infinity
      let top = Infinity
      svgPath.iterate((seg, i, x, y) => {
        if (!i) return
        if (x < left) {
          left = x
        }
        if (y < top) {
          top = y
        }
      })
      
      return svgPath.translate(-left + this.buffer, -top + this.buffer).scale(this.scale).toString()
    },
    bounds () {
      let width = 0
      let height = 0
      
      new SvgPath(this.path).iterate((seg, i, x, y) => {
        if (x > width) {
          width = x
        }
        if (y > height) {
          height = y
        }
      })
      
      return { width, height }
    }
  },
  mounted () {
    this.rc = rough.canvas(this.$refs.canvas)
    this.ctx = this.$refs.canvas.getContext('2d')
    
    // DPR stuff for mobile and retina displays
    let scale = window.devicePixelRatio
    let width = this.bounds.width + this.buffer * this.scale
    let height = this.bounds.height + this.buffer * this.scale
    
    this.$refs.canvas.width = width * scale
    this.$refs.canvas.height = height * scale
    this.$refs.canvas.style.width = width + 'px'
    this.$refs.canvas.style.height = height + 'px'
    this.ctx.scale(scale, scale)
    
    // A simple dynamic sizing method
    window.addEventListener('resize', this.resize, { passive: true })
    
    this.generate()
    this.draw()
  },
  watch: {
    icon (val) {
      this.resize()
      this.generate()
      this.draw()
    },
    prefix (val) {
      this.resize()
      this.generate()
      this.draw()
    },
    buffer (val) {
      this.resize()
      this.generate()
      this.draw()
    },
    scaling (val) {
      this.resize()
      this.generate()
      this.draw()
    },
    roughness (val) {
      this.drawable.options.roughness = val
      this.generate()
      this.draw()
    },
    bowing (val) {
      this.drawable.options.bowing = val
      this.generate()
      this.draw()
    },
    stroke (val) {
      this.drawable.options.stroke = val
      this.draw()
    },
    strokeWidth (val) {
      this.drawable.options.strokeWidth = val
      this.generate()
      this.draw()
    },
    fill (val) {
      this.drawable.options.fill = val
      this.draw()
    },
    fillStyle (val) {
      this.drawable.options.fillStyle = val
      this.generate()
      this.draw()
    },
    fillWeight (val) {
      this.drawable.options.fillWeight = val
      this.draw()
    },
    hachureAngle (val) {
      this.drawable.options.hachureAngle = val
      this.generate()
      this.draw()
    },
    hachureGap (val) {
      this.drawable.options.hachureGap = val
      this.generate()
      this.draw()
    },
    curveStepCount (val) {
      this.drawable.options.curveStepCount = val
      this.generate()
      this.draw()
    },
    simplification (val) {
      this.drawable.options.simplification = val
      this.generate()
      this.draw()
    }
  },
  methods: {
    generate () {
      this.drawable = this.rc.generator.path(this.path, this.options)
    },
    draw () {
      this.ctx.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height)
      this.rc.draw(this.drawable)
    },
    resize () {
      let scale = window.devicePixelRatio

      let oldWidth = this.$refs.canvas.width
      let oldHeight = this.$refs.canvas.height
      let newWidth = (this.bounds.width + this.buffer * this.scale) * scale
      let newHeight = (this.bounds.height + this.buffer * this.scale) * scale

      if (Math.abs(oldWidth - newWidth) >= 1 || Math.abs(oldHeight - newHeight) >= 1) {
        this.$refs.canvas.width = newWidth
        this.$refs.canvas.height = newHeight
        this.$refs.canvas.style.width = newWidth + 'px'
        this.$refs.canvas.style.height = newHeight + 'px'
        this.ctx.scale(scale, scale)

        this.generate()
        this.draw()
      }
    }
  }
})

// Point of entry
new Vue({
  el: '#app',
  data () {
    return {
      red: '#F44336',
      yellow: '#FFEB3B',
      green: '#4CAF50',
      blue300: '#64B5F6',
      blue: '#2196F3',
      blue800: '#1565C0',
      lightBlue100: '#B3E5FC',
      brown300: '#A1887F',
      brown400: '#8D6E63',
      brown: '#795548',
      grey: '#9E9E9E',
      white: '#FFFFFF',
    }
  }
})

External CSS

  1. https://codepen.io/milesmanners/pen/JLPRaG.scss
  2. https://fonts.googleapis.com/css?family=Open+Sans

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js
  2. https://rawgit.com/pshihn/rough/master/dist/rough.min.js
  3. https://codepen.io/milesmanners/pen/bd3d71cfa7f965b756b4ca789f3bfc42.js
  4. https://pro.fontawesome.com/releases/v5.3.1/js/all.js