70

I'm trying to make fade out effect for a div with pure JavaScript.

This is what I'm currently using:

//Imagine I want to fadeOut an element with id = "target"
function fadeOutEffect()
{
 var fadeTarget = document.getElementById("target");
 var fadeEffect = setInterval(function() {
  if (fadeTarget.style.opacity < 0.1)
  {
   clearInterval(fadeEffect);
  }
  else
  {
   fadeTarget.style.opacity -= 0.1;
  }
 }, 200);
}

The div should fade out smoothly, but it immediately disappears.

What's wrong? How can I solve it?

jsbin

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
Anakin
  • 1,233
  • 1
  • 14
  • 20

9 Answers9

125

Initially when there's no opacity set, the value will be an empty string, which will cause your arithmetic to fail. That is, "" < 0.1 == true and your code goes into the clearInterval branch.

You can default it to 1 and it will work.

function fadeOutEffect() {
    var fadeTarget = document.getElementById("target");
    var fadeEffect = setInterval(function () {
        if (!fadeTarget.style.opacity) {
            fadeTarget.style.opacity = 1;
        }
        if (fadeTarget.style.opacity > 0) {
            fadeTarget.style.opacity -= 0.1;
        } else {
            clearInterval(fadeEffect);
        }
    }, 200);
}

document.getElementById("target").addEventListener('click', fadeOutEffect);
#target {
    height: 100px;
    background-color: red;
}
<div id="target">Click to fade</div>

An empty string seems like it's treated as a 0 by JavaScript when doing arithmetic and comparisons (even though in CSS it treats that empty string as full opacity)

> '' < 0.1
> true

> '' > 0.1
> false


> '' - 0.1
> -0.1

Simpler Approach We can now use CSS transitions to make the fade out happen with a lot less code

const target = document.getElementById("target");

target.addEventListener('click', () => target.style.opacity = '0');
// If you want to remove it from the page after the fadeout
target.addEventListener('transitionend', () => target.remove());
#target {
    height: 100px;
    background-color: red;
    transition: opacity 1s;
}
<p>Some text before<p>
<div id="target">Click to fade</div>
<p>Some text after</p>
Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
33

Just this morning I found this piece of code at http://vanilla-js.com, it's very simple, compact and fast:

var s = document.getElementById('thing').style;
s.opacity = 1;
(function fade(){(s.opacity-=.1)<0?s.display="none":setTimeout(fade,40)})();

You can change the speed of the fade changing the second parameter in the setTimeOut function.

var s = document.getElementById('thing').style;
s.opacity = 1;
(function fade(){(s.opacity-=.1)<0?s.display="none":setTimeout(fade,40)})();
#thing {
  background: red;
  line-height: 40px;
}
<div id="thing">I will fade...</div>
Lucas Ferreira
  • 858
  • 8
  • 18
19

It looks like you can do it another way(I may be wrong).

event.target.style.transition = '0.8s';
event.target.style.opacity = 0;
Alexander Tarasenko
  • 662
  • 1
  • 8
  • 15
  • 1
    CSS transition works, it even works better in real life. But, I wanted a pure javascript-based animation. – Anakin May 20 '19 at 16:11
  • 1
    Why @Anakin? CSS is far more efficient than JS timers. – Hannes Schneidermayer Jun 07 '21 at 14:24
  • This thread is old. Back then, I was doing this solely for learning-purposes. In practical uses, I don't see a reason this should be implemented over CSS transition either. – Anakin Jul 10 '21 at 10:08
7

you can use CSS transition property rather than doing vai timer in javascript. thats more performance oriented compared to what you are doing.

check

http://fvsch.com/code/transition-fade/test5.html#test3

Vikash
  • 674
  • 1
  • 5
  • 11
  • While a valid point, this should be a comment, the OP wants to know what is wrong with their code, it was very close to working – Ruan Mendes Mar 12 '15 at 18:29
  • @JuanMendes :- i am new here. I dont have enough points to add comments. I can comment in my question and answer only. – Vikash Mar 12 '15 at 19:03
6

In addition to the accepted answer, we now have WAAPI which basically adds animation API to JavaScript.

For example,

/**
 * @returns {Object}
*/
function defaultFadeConfig() {
  return {      
      easing: 'linear', 
      iterations: 1, 
      direction: 'normal', 
      fill: 'forwards',
      delay: 0,
      endDelay: 0
    }  
}

/** 
 * @param {HTMLElement} el
 * @param {number} durationInMs
 * @param {Object} config
 * @returns {Promise}
 */
async function fadeOut(el, durationInMs, config = defaultFadeConfig()) {  
  return new Promise((resolve, reject) => {         
    const animation = el.animate([
      { opacity: '1' },
      { opacity: '0', offset: 0.5 },
      { opacity: '0', offset: 1 }
    ], {duration: durationInMs, ...config});
    animation.onfinish = () => resolve();
  })
}

/** 
 * @param {HTMLElement} el
 * @param {number} durationInMs
 * @param {Object} config
 * @returns {Promise}
 */
async function fadeIn(el, durationInMs, config = defaultFadeConfig()) {
  return new Promise((resolve) => {         
    const animation = el.animate([
      { opacity: '0' },
      { opacity: '0.5', offset: 0.5 },
      { opacity: '1', offset: 1 }
    ], {duration: durationInMs, ...config});
    animation.onfinish = () => resolve();
  });
}

window.addEventListener('load', async ()=> {
  const el = document.getElementById('el1');  
  for(const ipsum of "Neque porro quisquam est qui dolorem ipsum quia dolor \uD83D\uDE00".split(' ')) {
    await fadeOut(el, 1000);  
    el.innerText = ipsum;
    await fadeIn(el, 2000);
  }
});
.text-center {
  text-align: center;
}
<h1 id="el1" class="text-center">...</h1>
Alex Nolasco
  • 18,750
  • 9
  • 86
  • 81
  • 1
    FTW. 2022 and I hadn't heard of `animate()`! I love your use of `await` in a `for` loop to produce incredibly easy to read animation code. – Ruan Mendes Apr 13 '22 at 13:23
  • Much better than all other `setInterval` "solutions" – Anurag Srivastava Jul 13 '22 at 08:49
  • I really like this solution (just implemented it in one of my projects) thanks for sharing! Wonder if the browser compatibility is sufficient though when using on commercial websites? Has someone experiences with that or used a polyfill successfully? – Luckyfella Sep 07 '22 at 08:12
4

function fadeOutEffect() {
    var fadeTarget = document.getElementById("target");
    var fadeEffect = setInterval(function () {
        if (!fadeTarget.style.opacity) {
            fadeTarget.style.opacity = 1;
        }
        if (fadeTarget.style.opacity > 0) {
            fadeTarget.style.opacity -= 0.1;
        } else {
            clearInterval(fadeEffect);
        }
    }, 200);
}

document.getElementById("target").addEventListener('click', fadeOutEffect);
#target {
    height: 100px;
    background-color: red;
}
<div id="target">Click to fade</div>
mahdiros
  • 37
  • 3
0

my solution

function fadeOut(selector, timeInterval, callback = null) {

    var fadeTarget = document.querySelector(selector);

    let time = timeInterval / 1000;
    fadeTarget.style.transition = time + 's';
    fadeTarget.style.opacity = 0;
    var fadeEffect = setInterval(function() {

        if (fadeTarget.style.opacity <= 0)
        {
            clearInterval(fadeEffect);
            if (typeof (callback) === 'function') {
                callback();
            }
        }
    }, timeInterval);
}
Lex Jad
  • 61
  • 1
  • 2
0

While most of the solutions in this and other SO threads work, here are my two cents. If you want to rely only on pure JS ONLY then this should do the trick:

   function fadeOutEle(el) {
        el.style.opacity = 1;
        (function fade() {
            if ((el.style.opacity -= .009) < 0) {
                el.style.display = "none";
            } else {
                requestAnimationFrame(fade);
            }
        })();
    };

The Key here is the el.style.opacity -= .009 which give a cool fade out effect. Keep it below 1/2 a second (0.009 in this case) so that the effect is visible before the element hides.

Ajay Kumar
  • 2,906
  • 3
  • 23
  • 46
0

I needed some custom fading, so I came up with this solution. The methods are added to the HTMLElement prototype for an easier workflow. Supports a fadeIn to display:flex and can fadeIn to a predefined opacity value from data-op attribute(didn't need it for fadeOut, but that's an easy upgrade). The method returns this, for chaining..

(()=>{
    const ndt = () => +new Date(),
        anim = (f) => (window.requestAnimationFrame && requestAnimationFrame(f)) || setTimeout(f, 16),
        fader = (el, time, out, last, flex = false) => {
            if (!el.style.opacity) el.style.opacity = out ? '1' : '0';
            const op = el.dataset.op ?? '1';  
                hide = () => {
                    el.style.display = 'none';
                    el.style.opacity = '0';
                },
                show = (done) => {
                    el.style.display = !flex ? 'block' : 'flex';
                    if (done) el.style.opacity = op;
                },
                calc = (o, t) => out ? o - t : o + t,
                tick = () => {
                    el.style.opacity = calc(+el.style.opacity, (ndt() - last) / time);
                    last = ndt();
                    const o = +el.style.opacity,
                        a = out && o > 0 || !out && o < +op;
                    console.log('opacity', o)
                    if (!a) return out ? hide() : show(true);
                    anim(tick);
                };
            if (!out) show(false);
            tick();
        };
    HTMLElement.prototype.fadeIn = function (time, flex = false) {
        fader(this, time, false, ndt(), flex);
        return this;
    };
    HTMLElement.prototype.fadeOut = function (time) {
        fader(this, time, true, ndt());
        return this;
    };
})();

const parent = document.getElementById('parent');
document.getElementById('fadeIn').addEventListener('click', e=> {
  parent.fadeIn(1000, true);
});
document.getElementById('fadeOut').addEventListener('click', e=> {
  parent.fadeOut(1000);
});
div#parent {
  display: flex;
}
div#parent div {
  height: 300px;
  width: 300px;
}
div#red {
  background: red;
}
div#green {
  background: green;
}
<button id="fadeIn">fadeIn</button>
<button id="fadeOut">fadeOut</button>
<div id="parent" style="display:none;" data-op="0.5">
  <div id="red"></div>
  <div id="green"></div>
</div>