iuliu.net
iuliu.net

Reputation: 7165

.css() won't get applied after a delay

I wanted to change the background-color on a div dinamically using jQuery's css() and it worked, but then I tried to add some delay to it, and for some reason it stopped working. What am I missing? Here's an MVC of it:

HTML:

<div id="nodelay"></div>
<div id="delay"></div>

JS:

$("#nodelay").hover(function() {
       $(this).css("background-color", 'gray');
     });

$("#delay").hover(function() {
    setTimeout(function() {
        $(this).css("background-color", 'gray');
    }, 500);
});

https://jsfiddle.net/8eabfa2t/1/

Upvotes: 7

Views: 189

Answers (5)

Jhecht
Jhecht

Reputation: 4435

This is due to the scope of this in the function after the delay. There are a lot of ways to get passed this, and everyone has their preference. My preferred method is using the Function.prototype.bind() method. It binds the function's this reference to whatever you pass it as the first object.

The reason this works is hard for me to explain (it makes a lot of sense in my head), but the easiest way I can say it is that the this being passed to .bind() is the same this you would call from the .hover() call.

$("#nodelay").hover(function() {
  $(this).css("background-color", 'gray');
});

$("#delay").hover(function() {
  setTimeout(function() {
    $(this).css("background-color", 'gray');
  }.bind(this), 500);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="nodelay">t</div>
<div id="delay">t</div>

Another way that people do it is by declaring a variable outside of the function where the this call is being messed up. Because of how scope in Javascript works, any functions declared after that variable can access it.

$("#nodelay").hover(function() {
  $(this).css("background-color", 'gray');
});

$("#delay").hover(function() {
  var self = this;
  setTimeout(function() {
    $(self).css("background-color", 'gray');
  }, 500);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="nodelay">t</div>
<div id="delay">t</div>

Upvotes: 3

Andrew Li
Andrew Li

Reputation: 57982

From the MDN documentation:

The "this" problem

Code executed by setTimeout() is called from a separate execution context to the function from which setTimeout was called. The usual rules for setting the this keyword for the called function apply, and if you have not set this in the call or with bind, it will default to the global (or window) object in non–strict mode, or be undefined in strict mode. It will not be the same as the this value for the function that called setTimeout. (Emphasis mine)

Since functions passed to setTimeout are executed in a different context, this is not bound. That would mean this actually refers to window (or undefined in strict mode). You are essentially doing $(window).css(...) which is not intended.

To combat this, you may use Function.prototype.bind to bind this context as mentioned above. From the documentation:

The bind() method creates a new function that, when called, has its this keyword set to the provided value

Since this outside of the setTimeout function is the element (as jQuery does this for you via explicit this binding like we're doing here), using $(this) will refer to the #delay element:

$("#nodelay").hover(function() {
  $(this).css("background-color", 'gray');
});

$("#delay").hover(function() {
  setTimeout(function() {
    $("#delay").css("background-color", 'gray');
  }.bind(this), 500);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="nodelay">Test</div>
<div id="delay">Test</div>

You can alternatively, as mentioned, capture this before you enter the anonymous function, or explicitly mention the element in the selector. If you're using ES6, another way to do this is to use arrow functions, which do not bind their own this, referring to that of the enclosing context.

Upvotes: 7

printfmyname
printfmyname

Reputation: 971

Great answers from others. Since everyone else pointed out problem with use of 'this' context, I won't bother to explain it more. However if I may, I would like to introduce you to an alternative and very useful jQuery $.proxy() function. It allows you to set the context of your choice. First argument to proxy function is a function to be executed. Second argument is the context, then what ever the argument you need to parse to the function. Try the example below

$("#nodelay").hover(function() {
       $(this).css("background-color", 'gray');
     });

$("#delay").hover(function() {
    setTimeout($.proxy(function() {
        $(this).css("background-color", 'gray');
    }, this) , 500);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="nodelay">t</div>
<div id="delay">t</div>

Upvotes: 0

yezzz
yezzz

Reputation: 3020

That's not due to timeout, but related to the scope of this.

https://jsfiddle.net/8eabfa2t/4/

Upvotes: 0

abigwonderful
abigwonderful

Reputation: 1927

helpfule reading: https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout#The_this_problem (if that link doesn't take you right to the 'the "this" problem, search for it on the page)

Does this work for you:

$("#delay").hover(function() {
    var target = $(this);
   setTimeout(function() {
     target.css("background-color", 'gray');
   }, 500);
 });

Upvotes: 0

Related Questions