Build a Reactive Trochoidal Wave with SVG and Vue

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!