'How to fade smoothly between gradients in JavaScript
This code populates a div with a predefined set of gradients and fades through them in a cycle using jQuery's .animate() method:
/// Background Gradient Cycler
var gradients = [
['#9eb5d7', '#242424'],
['#efe2ae', '#a8acc9'],
['#6f7554', '#eee1ad']
]
var gradientsRev = gradients.reverse()
var gradientCover = document.getElementById('gradientCover');
for (var g = 0; g < gradientsRev.length; g++) {
var gradEl = document.createElement('div')
gradEl.className = 'gradient'
gradEl.style.background = `linear-gradient(${gradientsRev[g][0]}, ${gradientsRev[g][1]})`;
gradientCover.appendChild(gradEl)
}
var gradientEls = document.querySelectorAll('#gradientCover .gradient')
function gradientCycler() {
function gradeFade(i, opDest) {
var fadeDur = 20000
$(gradientEls[i]).animate({
'opacity': opDest
}, {
duration: fadeDur,
complete: function() {
if (parseInt(i) > 1) {
if (parseInt(opDest) === 0) gradeFade(i - 1, 0)
else gradFadeStart()
} else {
gradeFade(gradientEls.length - 1, 1)
}
}
})
}
var gradFadeStart = function() {
$('.gradient').css('opacity', 1);
gradeFade(gradientEls.length - 1, 0)
}
gradFadeStart()
}
gradientCycler()
body {
margin: 0;
overflow: hidden;
}
#gradientCover,
.gradient {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
}
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<div id="page">
<div id="gradientCover"></div>
</div>
The problem is that the transition at certain parts is visibly choppy, with banding artifacts --
What can be done to reduce this artifacting so that the transition between the gradients appears smoother and less choppy?
Solution 1:[1]
I think is related to color depth, As a former CG artist I have seen these "artefacts" in software like Maya and Photoshop, to solve the problem it was necessary to increase the number of bits per channel (in Photoshop going from 8 Bits/Channel to 16).
Normally, this issue of bands appears when the two colors of the gradient are close (in term of RGB values) because there are few values of colors available between these two colors
If the gradient is rendered OR displayed (due to the monitor limitation) at a low number of bits per channel these banding effect can appear.
You can check your monitor color depth here.
You can also apply CSS according to this value:
if (screen.colorDepth <= 8)
//simple blue background color for 8 bit screens
document.body.style.background = "#0000FF"
else
//fancy blue background color for modern screens
document.body.style.background = "#87CEFA"
Solution 2:[2]
Including my codepen tests here as an answer just in case it helps anyone. I managed to get some improved performance by pre-rendering PNGs with the canvas' .toDataURL()
method and animating through them in jQuery (also achieved about the same performance with this method but using Three.js):
Here's a working example of the JQuery method:
/// Background Gradient Cycler
var gradients = [
['#9eb5d7', '#242424'],
['#efe2ae', '#a8acc9'],
['#6f7554', '#eee1ad']
]
var gradientsRev = gradients.reverse()
var gradientCover = document.getElementById('gradientCover');
for (var g = 0; g < gradientsRev.length; g++) {
var gradEl = document.createElement('div')
gradEl.className = 'gradient'
var gradCanv = document.createElement('canvas')
gradCanv.className = 'gradient'
var ctx = gradCanv.getContext("2d");
var grd = ctx.createLinearGradient(0, 0, 0, gradCanv.height);
grd.addColorStop(0, gradients[g][0]);
grd.addColorStop(1, gradients[g][1]);
ctx.fillStyle = grd;
ctx.fillRect(0, 0, gradCanv.width, gradCanv.height);
var gradIm = gradCanv.toDataURL("img/png")
gradEl.style.backgroundImage = `url(${gradIm})`
gradientCover.appendChild(gradEl)
}
var gradientEls = document.querySelectorAll('#gradientCover .gradient')
function gradientCycler() {
function gradeFade(i, opDest) {
var fadeDur = 20000
$(gradientEls[i]).animate({
'opacity': opDest
}, {
duration: fadeDur,
complete: function() {
if (parseInt(i) > 1) {
if (parseInt(opDest) === 0) gradeFade(i - 1, 0)
else gradFadeStart()
} else {
gradeFade(gradientEls.length - 1, 1)
}
}
})
}
var gradFadeStart = function() {
$('.gradient').css('opacity', 1);
gradeFade(gradientEls.length - 1, 0)
}
gradFadeStart()
}
gradientCycler()
body {
margin: 0;
overflow: hidden;
}
#gradientCover,
.gradient {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-size: 100%;
}
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<div id="page">
<div id="gradientCover"></div>
</div>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Ben Souchet |
Solution 2 |