Creating lightnings with JavaScript and HTML5 canvas
I recently created the following pen:
In this post I will tell you how to create lightning effects like this.
The lightning
The lightning is a set of lines which start from a set point (the top of the lightning) and go towards a random point (The end of the lightning). To make the lightning less straight I used a noise.
If you've ever worked with algorithms like the diamond-square algorithm, then the following part will be familiar for you. First I start off with a straight line from the top of the lightning to a randomly chosen point close to the bottom of the canvas.
Then I get the middle of the segment, I add a random value to the x position of it and I insert it between the start end end of the segment.
I do the above method to each segment of the lightning, then I repeat it again lowering the maximum of the random value each time I go trough the whole lightning, so the lightning doesn't look too rough. This makes the lightning have a well defined shape (To see what would happen if I leave out this step go to the pen and change the "roughness" variable to 1).
Here's a poorly drawn image about what's happening:
The beauty about this is that this noise function can be used for many different things, most notably for terrain generation.
So, if you want to implement this yourself you need to define some values:
- Roughness: This value determines how rough the lightning will be. Smaller values will give you rougher curves. You shouldn't go below 1.5.
- Minimum segment height: The algorithm will divide the segments until every segment's height is less than or equal to this value. This also tells the algorithm how detailed the curve should be.
- Max difference: The starting value for the random function. This is the maximum distance a point can differ from the middle point of the segment it sits on.
In my pen I used the following values for this 3 variable:
var roughness = 2;
var maxDifference = size / 5; // "size" is the width and height of the canvas.
var minSegmentHeight = 5;
Next, the actual algorithm. The segments are simply defined by 2 points.
// If you're using this in your projects, then I'd recommend creating a separate object for this.
function createLightning() {
// The main segment's height
var y = groundHeight + (Math.random() - 0.9) * 50;
var segmentHeight = y - center.y;
var lightning = [];
// The start and the end position of the lightning.
lightning.push({x: center.x, y: center.y});
lightning.push({x: Math.random() * (size - 100) + 50, y: y});
// This is important so we don't change the global one.
var currDiff = maxDifference;
while (segmentHeight > minSegmentHeight) {
// This uses the double buffering pattern
var newSegments = [];
for (var i = 0; i < lightning.length - 1; i++) {
// The start and the end position of the current segment
var start = lightning[i];
var end = lightning[i + 1];
// "midX" is the average X position of the segment
var midX = (start.x + end.x) / 2;
var newX = midX + (Math.random() * 2 - 1) * currDiff;
// Add the start and the middle point to the new segment list
// Because the end position is going to be added again in the next iteration
// we don't need to add that here.
newSegments.push(start, {x: newX, y: (start.y + end.y) / 2});
}
// Add the last point of the lightning to the segments.
newSegments.push(lightning.pop());
lightning = newSegments;
currDiff /= roughness;
segmentHeight /= 2;
}
return lightning;
}
Drawing the lightning
The lightning are drawn using the canvas' path functions. The lightning's color is a shade of electrical blue (hsl(180, 80%, 80%)
).
Drawing the ligthning involves few tricks. The first is a well known one with a twist. By setting the clear color's opacity to 0.2 the older lightnings instead of disappearing completely they fade out slowly. I only draw 1 new lightning every frame, but it looks like there's always 20 at least on the screen.
This trick has a problem though. The canvas never gets cleared completely and you could see where older lightnings were. There's however a quick way to solve this. I set the fill style to the color of the lightnings and I fill the canvas. This way the background color changes and the lightnings won't stay there. To see what would happen if I leave out this try to comment out the 20th line.
The next trick involves setting the context's globalCompositeOperation
property to "lighter". This makes the parts where multiple lightnings are drawn whiter, like the start of the lightning. You probably didn't notice this but you would miss it if it wasn't there. Again, to see what happens without this feature, comment out the 27th line.
The last trick is the slight gloom effect around the lightnings. This is done by simply setting the context's shadowColor
property to the color of the lightning and the shadowBlur
property to 15. To turn it off, comment out the 28th line.