Reputation: 15
I am trying to make a sine wave on a canvas that would be filled with a certain color. I was successful with creating the wave animation. However, i can't seem to get the fill right. You can see the way my code works in the snippet below
const canvas = document.querySelector('canvas')
const gui = new dat.GUI()
const c = canvas.getContext('2d')
canvas.width = innerWidth
canvas.height = innerHeight
const wave = {
y: canvas.height / 2,
length: 0.01,
amplitude: 150,
frequency: 0.01
}
const strokeColor = {
h: 200,
s: 50,
l: 50
}
const backgroundColor = {
r: 255,
g: 50,
b: 255,
a: 1
}
const waveFolder = gui.addFolder('wave')
waveFolder.add(wave, 'y', 0, canvas.height)
waveFolder.add(wave, 'length', -0.01, 0.01)
waveFolder.add(wave, 'amplitude', -300, 300)
waveFolder.add(wave, 'frequency', -0.01, 1)
const strokeFolder = gui.addFolder('stroke')
strokeFolder.add(strokeColor, 'h', 0, 255)
strokeFolder.add(strokeColor, 's', 0, 100)
strokeFolder.add(strokeColor, 'l', 0, 100)
const backgroundFolder = gui.addFolder('background')
backgroundFolder.add(backgroundColor, 'r', 0, 255)
backgroundFolder.add(backgroundColor, 'g', 0, 255)
backgroundFolder.add(backgroundColor, 'b', 0, 255)
backgroundFolder.add(backgroundColor, 'a', 0, 1)
let increment = 0
function drawSineWave() {
c.fillStyle = `rgba(${backgroundColor.r},${backgroundColor.g},${backgroundColor.b},${backgroundColor.a})`
c.clearRect(0, 0, canvas.width, canvas.height)
increment++
c.beginPath()
c.moveTo(0, canvas.height / 2)
c.lineTo(0, canvas.height / 2)
c.lineTo(0, canvas.height)
c.lineTo(canvas.width, canvas.height)
c.lineTo(canvas.width, canvas.height / 2)
c.moveTo(0, canvas.height / 2)
for (let i = 0; i < canvas.width; i++) {
c.lineTo(i, wave.y + Math.sin(increment / 50) * wave.amplitude * Math.sin(i * wave.length + wave.frequency))
}
c.lineTo(canvas.width, canvas.height / 2)
c.strokeStyle = `hsl(${strokeColor.h},${strokeColor.s}%,${strokeColor.l}%)`
c.fill()
c.stroke()
}
function animate() {
requestAnimationFrame(animate)
drawSineWave()
}
animate()
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
canvas {
width: 100%;
height: 100%;
}
<head>
<title>Waves</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<canvas></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.min.js"></script>
<script src="index.js"></script>
</body>
The way I want the wave to look is this:
Can someone please help me achieve the desired result? Thanks!
Upvotes: 0
Views: 1273
Reputation: 54026
You just need to draw the bottom half in the correct order
The result is
function drawSineWave() {
c.fillStyle = `rgba(${backgroundColor.r},${backgroundColor.g},${backgroundColor.b},${backgroundColor.a})`
c.clearRect(0, 0, canvas.width, canvas.height)
increment++
c.beginPath()
for (let i = 0; i < canvas.width; i++) {
c.lineTo(i, wave.y + Math.sin(increment / 50) * wave.amplitude * Math.sin(i * wave.length + wave.frequency))
}
c.strokeStyle = `hsl(${strokeColor.h},${strokeColor.s}%,${strokeColor.l}%)`
c.stroke(); // draw stroke along wave top only
c.lineTo(canvas.width, canvas.height) // bottom right
c.lineTo(0, canvas.height) // bottom left
c.fill()
}
I played about with it a bit.
const canvas = document.querySelector('canvas')
const gui = new dat.GUI()
const c = canvas.getContext('2d')
canvas.width = innerWidth
canvas.height = innerHeight
const wave = {
offset: canvas.height/2,
speed: 1,
amplitude:50,
frequency: 1,
}
var disFreq = wave.frequency, disFreqChase = 0;
var disAmp = wave.amplitude, disAmpChase = 0;
var disY = wave.offset, disYChase = 0;
var disSp = wave.speed, disSpChase = 0;
const strokeColor = {width: 2, h:200,s:50, l:50 }
const backgroundColor = { r:255, g:50, b:255, a:1 }
const waveFolder = gui.addFolder('wave')
waveFolder.add(wave,'speed',-1,1); // peeks per second
waveFolder.add(wave,'offset',0,canvas.height);
waveFolder.add(wave,'amplitude',-100,100)
waveFolder.add(wave,'frequency',0.5,10); // frequency also defines wave length
// Value is fraction of canvas width
const strokeFolder = gui.addFolder('stroke')
strokeFolder.add(strokeColor,'width',0,18)
strokeFolder.add(strokeColor,'h',0,255)
strokeFolder.add(strokeColor,'s',0,100)
strokeFolder.add(strokeColor,'l',0,100)
const backgroundFolder = gui.addFolder('background')
backgroundFolder.add(backgroundColor,'r',0,255)
backgroundFolder.add(backgroundColor,'g',0,255)
backgroundFolder.add(backgroundColor,'b',0,255)
backgroundFolder.add(backgroundColor,'a',0,1)
let increment = 0
function drawSineWave() {
// smooth out changes
disFreq += disFreqChase = (disFreqChase += (wave.frequency - disFreq)*0.2)* 0.2;
disAmp += disAmpChase = (disAmpChase += (wave.amplitude - disAmp) * 0.2) * 0.2;
disY += disYChase = (disYChase += (wave.offset - disY) * 0.2) * 0.2;
disSp += disSpChase = (disSpChase += (wave.speed - disSp) * 0.2) * 0.2;
c.lineWidth = strokeColor.width;
c.fillStyle = `rgba(${backgroundColor.r},${backgroundColor.g},${backgroundColor.b},${backgroundColor.a})`
c.clearRect(0,0,canvas.width,canvas.height)
//increment++
increment += (disSp / 60);
c.beginPath()
const h = canvas.height;
for (let i=0;i<canvas.width;i++){
const a = (disAmp / 200) * h;
const x = ((i / canvas.width) * disFreq) % 1 + increment;
const p = x * Math.PI * 2;
c.lineTo(i, disY + Math.sin(p)*a);
}
c.strokeStyle = `hsl(${strokeColor.h},${strokeColor.s}%,${strokeColor.l}%)`
c.stroke()
c.lineTo(canvas.width,canvas.height)
c.lineTo(0, canvas.height)
c.fill()
}
function animate(){
requestAnimationFrame(animate)
drawSineWave()
}
animate()
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
canvas{
width: 100%;
height: 100%;
}
<canvas></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.min.js"></script>
<script src="index.js"></script>
</body>
Upvotes: 1