Tips4Devs.com

BOOKS

Build a Reactive Trochoidal Wave with SVG and Vue

last updated on 2020-02-09

This tutorial will cover the concept of Trochoidal Wave and visualize it with the reactive framework Vue.js.

Style and Responsive ViewBox

The demo is built using SVG to take advantage of its viewBox property which allows us to set an original dimension and let the browser handle the resizing according to the preserveAspectRatio rule we set, in this case our SVG uses the following: preserveAspectRatio="xMidYMid meet"

So all we got left to do in term of layout is make sure the SVG fills the screen width, and we will let the SVG fill the height based on its viewBox property:

body {
  background: black;
  margin: 0;
  svg#app {
    width: 100vw;
    overflow: hidden;
  }
}

The template

Next we create the Vue template which will consist of a few for loops to render all the points

The Reactive Component

Because we're using computed properties to generate the SVG elements, our loop method has very little complexity. All we have to do is update the current time and Vue JS takes care of updating the depending computed values and rendering accordingly.

export default {
  name: 'App',
  data: () => ({
    lines: [],      // number of wave lines
    resolution: 8,  // number of point per wave line
    width: window.innerWidth,
    height: window.innerHeight,
    time: 0
  }),
  created () {
    // loop over 3 lines and generate each points original X, Y and Radius property
    for (let lineIndex = 0, numLines = 3; lineIndex < numLines; lineIndex += 1) {
      this.lines.push([])
      for (let pointIndex = 0, X = this.resolution + 1; pointIndex < X; pointIndex += 1) {
        const radius = window.innerWidth / 20 * (0.2 + (1 -lineIndex / numLines) * 0.8)
        const scale = this.width / this.resolution
        const x = (pointIndex + 1) * scale
        const y = this.height / 2
        this.lines[lineIndex].push({x, y, radius})
      }
    }
    // start looping
    this.loop(0)
  },
  methods: {
    // in the loop function, all we do is update the time value, 
    // the data will react from that change and update the SVG
    loop (time) {
      window.requestAnimationFrame(this.loop)
      this.time = time * 0.003
    },
    // generate a string of coordinates in SVG polyline ready syntax
    pointsToString (lineIndex) {
      const points = []
      const lines = this.animatedLines
      // bottom left fixed point
      points.push({x: 0, y: this.height * 2})
      // left moving point
      points.push({x: 0, y: lines[lineIndex][0].y})
      points.push(...lines[lineIndex])
      // right moving point
      points.push({x: this.width, y: lines[lineIndex][lines[lineIndex].length - 1].y})
      // bottom right fixed point
      points.push({x: this.width, y: this.height * 2})
      return points.map(pt => `${pt.x},${pt.y}`)
    }
  },
  computed: {
    // we never change the original X and Y properties of each point,
    // instead we generate the projected values 
    // using Math.sin/cos and the current time value
    animatedLines () {
      const step = this.width / this.resolution
      return this.lines.map(line => line.map((point, pointIndex) => ({
        x: step * pointIndex + Math.sin(this.time + pointIndex) * point.radius,
        y: this.height / 2 + Math.cos(this.time + pointIndex) * point.radius
      })))
    },
    // Make sure scaling happens accordingly to our set dimensions
    viewBox () {
      return `0 0 ${this.width} ${this.height}`
    }
  }
}

Source Code

Checkout the demo or the full source here

That's about it! Hope you enjoyed this tutorial, if you have any question or comment, please use the section below or reach out on twitter. Enjoy!

Join The Conversation