Merry Christmas!

4 min read Original article ↗
              
                /************************************
  This codepen is part of the svg.js
  advent calendar. You can find all
  the pens at twitter: @svg_js
*************************************/

const width = window.innerWidth - 10
const height = window.innerHeight - 10

const canvas = SVG()
  .addTo('body')
  .size(width, height)
  .viewbox(0, 0, 1000, 500)


// Create a general blur filter for lots of stuff
const blurFilter = canvas.filter()
blurFilter.gaussianBlur(1)
blurFilter.move(-5, -5).size(11, 11)

// Create a gradient for the sky
const skyGradient = canvas.gradient('linear', (gradient) => {
  gradient.stop(0, '#36383f')
  gradient.stop(1, '#151154')
  gradient.rotate(90)
})

// Create sky
const sky = canvas.rect(1000, 300)
  .fill(skyGradient)
  .filterWith(blurFilter)

// Create a gradient for the snow
const snowGradient = canvas.gradient('linear', (gradient) => {
  gradient.stop(0, '#bbd')
  gradient.stop(1, '#fff')
  gradient.rotate(90)
})

// Create snow
const snow = canvas.rect(1000, 200).move(0, 300)
  .fill(snowGradient)
  .filterWith(blurFilter)

// Create arc path template
const template = canvas.path('M200 300A300 250 0 0 1 800 300')
  .stroke('black')
  .fill('none')
  .hide()

// Create sparkling arc
const arcPath = canvas.path('M200 300A300 250 0 0 1 200 300')
  .stroke({color: 'yellow', width: 20})
  .fill('none')
  .filterWith((filter) => {
    filter.gaussianBlur(10)
    filter.move(-5, -5).size(11, 11)
  })

// Calcualte arc length
const pathLength = template.length()

// This circle is the sparcling effect
const circle = canvas.circle(25)
  .center(200, 300)
  .fill('yellow')
  .filterWith((filter) => {
    // Create blur filter
    const blur = filter.gaussianBlur(10)

    // Turbulence and color filter
    var temp = filter.turbulence(0.15, 3)
      .colorMatrix('hueRotate', 0)

    // We animate the hueRotate value
    temp.animate(500)
      .ease('-')
      .attr('values', 360)
      .loop()

    // Use the created bump map to pixelate the blur
    filter.displacementMap(blur, temp, 10, 'R', 'G')

    // Values are factors to 100%. So 1.4 is 140%
    filter.move(-5, -5).size(11, 11)
    filter._animation = temp.timeline()
  })

const animateArc = () => {
  circle.animate(3000).during((pos) => {
    // Calculate point on path
    const {x, y} = template.pointAt(pos * pathLength)
    // Move circle and update path
    circle.center(x, y)
    arcPath.attr('d', `M200 300A300 250 0 0 1 ${x} ${y}`)
  }).after(() => {
    circle.animate().opacity(0).after(() => {
      // Stop the animation so we dont end up with
      // detached elements
      const filter = circle.reference('filter')
      filter._animation.stop()
      filter.remove()
      circle.remove()
    })
  })
}

// Create Merry Christmas text
const merryChristmasText = canvas.text('Merry Christmas')
  .filterWith((filter) => {
    const blur = filter.gaussianBlur(10)
    filter.blend(filter.source, blur)
    filter.move(-5, -5).size(11, 11)
  })
  .fill('white')
  .font({size:40})
  .center(500, 230)
  .opacity(0)

// Displays Merry Christmas text
const merryChristmas = () => {
  merryChristmasText
    .animate(5000)
    .opacity(1)
}

// Polygon for a house
const house = [
  [0, 0],
  [0, 10],
  [5, 20],
  [10, 10],
  [10, 0],
  [0, 10],
  [10, 10],
  [0, 0],
  [10, 0]
]

// Create basic dropShadow
const dropShadow = canvas.filter()
const blur = dropShadow.gaussianBlur(1)
dropShadow.blend(dropShadow.source, blur)
dropShadow.move(-5, -5).size(11, 11)

// initial polygon for house
const houseTemplate = new SVG.Polyline().plot([
  [0, 0],
  [0, 0],
  [5, 0],
  [10, 0],
  [10, 0],
  [0, 0],
  [10, 0],
  [0, 0],
  [10, 0]
]).fill('none')

// Set x positions for houses
const xPositions = [250, 600, 370, 700, 450, 520, 640, 220, 300, 780, 400]
let numHouses = 0
// Function to spawn one house
const spawnHouse = () => {
  if (numHouses > 8) return merryChristmas()

  const x = xPositions[numHouses]
  const y = numHouses / 10 * 100 + 340
  const scale = (y-300) / 200 * 10

  houseTemplate.clone().addTo(canvas).stroke({
    color: SVG.Color.random(),
    linejoin: 'round',
    linecap: 'round',
    width: 1
  }).transform({
    rotate: 180,
    scale: scale,
    origin: [0,Math.random()*3],
    translate: [x, y]
  }).animate(new SVG.Spring(500, 20)).plot(house)

  SVG.Animator.timeout(spawnHouse, 300)
  numHouses++
}


// Creates a circle of random size and fades it in
const createStar = () => {
  const x = Math.random() * 1000
  const y = Math.random() * 250

  const size = Math.random() * 15
  canvas.circle({r: size/2})
    .fill('yellow')
    .center(x, y)
    .opacity(0)
    .animate(2000)
    .opacity(1)
    .delay(500)
    // After fading in and delay
    // we twinkle the star away
    .after(function () {
      SVG.Animator.immediate(() => {
        twinkle(x, y, size)
        this.element().remove()
      })
    })
}

// Template for a star
const twinkleTemplate = new SVG.Polygon().plot([-12, 0, -2, 2, 0, 12, 2, 2, 12, 0, 2, -2, 0, -12, -2, -2])
  .fill('#f3eccf')
  .filterWith(dropShadow)

// Creates a polygon and animates its scale
// while fading out
const twinkle = (x, y, size) => {
  const clone = twinkleTemplate.clone()
    .addTo(canvas)
    .transform({
      scale: size / 10,
      origin: [0, 0],
      translate: [x, y]
    })

  clone.animate(1000)
    .during((pos) => { clone.transform({scale: size / 10 + size / 3 * pos, origin: [0, 0], translate: [x, y]}) })
    .opacity(0)
    .after(function () {
      this.element().remove()
    })
}



// Give a bit time before starting the animation
SVG.Animator.timeout(() => {
  animateArc()
  spawnHouse()

  // Every 100ms we decide if we want to create
  // a new star and after which time
  setInterval(() => {
    if (Math.random() < 0.7) {
      SVG.Animator.timeout(() => {
        createStar()
      }, Math.random() * 1000)
    }
  }, 300)
}, 1000)

              
            

!