10

I want a transition a css property smoothly then I want immediate change in css property value and then I want to attach the transition back again. To better understand see the following example:

if ($(".marquee").height() < $(".marquee-content").outerHeight(true)) {
  $(".marquee-content").clone().appendTo($(".marquee-wrapper"));
}
$('.marquee-wrapper').css("transition", "transform 3s linear");
$('.marquee-wrapper').css("transform", "translateY(-" + $(".marquee-content").outerHeight(true) + "px)");

setInterval(function() {
  
  $('.marquee-wrapper').css("transition", "none");
  $('.marquee-wrapper').css("transform", "translateY(100px)"); //This should Immediately change translateY to 100px without smooth transition. But this doesn't happen without adding a delay before the below written line
  
  // Its weird why javascript engine executes the below line before executing this line

  $('.marquee-wrapper').css("transition", "transform 3s linear");
  $('.marquee-wrapper').css("transform", "translateY(-" + $(".marquee-content").outerHeight(true) + "px)");

}, 3000);
.marquee {
  margin: auto;
  width: 600px;
  height: 200px;
  overflow: auto;
}

.marquee-wrapper {
  transform: translateY(0);
}

.marquee-content {
  margin: 0;
  padding: 30px 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<section class="marquee">
  <div class="marquee-wrapper">
    <div class="marquee-content">
      Updates: Update (8 Mar 2016): Now plugin have new option: startVisible The marquee will be visible in the start if set to true. Thanks to @nuke-ellington 👠Update (24 Jan 2014): Note: people who been asking me how to use this plugin with content being
      loaded with Ajax, please read notes about this update. New methods added, so now after you start the plugin using var $mq = $('.marquee').marquee();, you start the plugin using var $mq = $('.marquee').marquee();, you start the plugin using var $mq
      = $('.marquee').marquee();, then you can pause, resume, togglepause, resume) and desestroy destroy toggle(pause, resume) and destroy toggle(pause, resume) and destroy methods e.g to remove the marquee plugin from your element simply use $mq.marquee('destroy');.
      Similarly you can use pause the marquee any time using $mq.marquee('pause');.
    </div>
  </div>
</section>

As you can see in the setInterval I first set transition to none then translateY to 100px. Now in principle this should suddenly translate the div to 100px but this doesn't happen before moving div to 100px javascript engine executes the next line and reassign transition. In the below example I have given a 100ms delay before reassigning the transition and it works:

if ($(".marquee").height() < $(".marquee-content").outerHeight(true)) {
  $(".marquee-content").clone().appendTo($(".marquee-wrapper"));
}
$('.marquee-wrapper').css("transition", "transform 3s linear");
$('.marquee-wrapper').css("transform", "translateY(-" + $(".marquee-content").outerHeight(true) + "px)");

setInterval(function() {
  
  $('.marquee-wrapper').css("transition", "none");
  $('.marquee-wrapper').css("transform", "translateY(100px)"); //This  Immedeately change translateY to 100px without smooth transition now

  setTimeout(function(){
      $('.marquee-wrapper').css("transition", "transform 3s linear");
      $('.marquee-wrapper').css("transform", "translateY(-" + $(".marquee-content").outerHeight(true) + "px)");
  },100);
}, 3000);
.marquee {
  margin: auto;
  width: 600px;
  height: 200px;
  overflow: auto;
}

.marquee-wrapper {
  transform: translateY(0);
}

.marquee-content {
  margin: 0;
  padding: 30px 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<section class="marquee">
  <div class="marquee-wrapper">
    <div class="marquee-content">
      Updates: Update (8 Mar 2016): Now plugin have new option: startVisible The marquee will be visible in the start if set to true. Thanks to @nuke-ellington 👠Update (24 Jan 2014): Note: people who been asking me how to use this plugin with content being
      loaded with Ajax, please read notes about this update. New methods added, so now after you start the plugin using var $mq = $('.marquee').marquee();, you start the plugin using var $mq = $('.marquee').marquee();, you start the plugin using var $mq
      = $('.marquee').marquee();, then you can pause, resume, togglepause, resume) and desestroy destroy toggle(pause, resume) and destroy toggle(pause, resume) and destroy methods e.g to remove the marquee plugin from your element simply use $mq.marquee('destroy');.
      Similarly you can use pause the marquee any time using $mq.marquee('pause');.
    </div>
  </div>
</section>

My questions are:

  1. How do I stop javscript engine to reassign transition property before making changing translate property, without any delay?
  2. Why does javscript engine excutes a forthcoming line($('.marquee-wrapper').css("transition", "transform 3s linear");) in script before the current line($('.marquee-wrapper').css("transform", "translateY(100px)");)
Evhz
  • 8,852
  • 9
  • 51
  • 69
user31782
  • 7,087
  • 14
  • 68
  • 143
  • What you've described is likely not the right way to go about what you are trying to achieve, but check out https://developer.mozilla.org/en-US/docs/Web/Events/animationend the event api for animations – sheriffderek Mar 24 '17 at 19:25
  • I would use velocity.js. CSS transitions are only really great for small things, and despite what you may read... velocity and gsap are more performant than css transition. – sheriffderek Mar 24 '17 at 19:27

3 Answers3

11

Grouping the transition and transform CSS properties in a single statement gives the correct result, without having to use the 100 ms delay:

$('.marquee-wrapper').css({ transition: "transform 3s linear", transform: "translateY(-" + $(".marquee-content").outerHeight(true) + "px)" });
setInterval(function () {
    $('.marquee-wrapper').css({ transition: "none", transform: "translateY(100px)" });
    $('.marquee-wrapper').css({ transition: "transform 3s linear", transform: "translateY(-" + $(".marquee-content").outerHeight(true) + "px)" });
}, 3000);

if ($(".marquee").height() < $(".marquee-content").outerHeight(true)) {
    $(".marquee-content").clone().appendTo($(".marquee-wrapper"));
}

$('.marquee-wrapper').css({ transition: "transform 3s linear", transform: "translateY(-" + $(".marquee-content").outerHeight(true) + "px)" });

setInterval(function () {
    $('.marquee-wrapper').css({ transition: "none", transform: "translateY(100px)" });
    $('.marquee-wrapper').css({ transition: "transform 3s linear", transform: "translateY(-" + $(".marquee-content").outerHeight(true) + "px)" });
}, 3000);
.marquee {
  margin: auto;
  width: 600px;
  height: 200px;
  overflow: auto;
}

.marquee-wrapper {
  transform: translateY(0);
}

.marquee-content {
  margin: 0;
  padding: 30px 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<section class="marquee">
  <div class="marquee-wrapper">
    <div class="marquee-content">
      Updates: Update (8 Mar 2016): Now plugin have new option: startVisible The marquee will be visible in the start if set to true. Thanks to @nuke-ellington 👠Update (24 Jan 2014): Note: people who been asking me how to use this plugin with content being
      loaded with Ajax, please read notes about this update. New methods added, so now after you start the plugin using var $mq = $('.marquee').marquee();, you start the plugin using var $mq = $('.marquee').marquee();, you start the plugin using var $mq
      = $('.marquee').marquee();, then you can pause, resume, togglepause, resume) and desestroy destroy toggle(pause, resume) and destroy toggle(pause, resume) and destroy methods e.g to remove the marquee plugin from your element simply use $mq.marquee('destroy');.
      Similarly you can use pause the marquee any time using $mq.marquee('pause');.
    </div>
  </div>
</section>

The reason for that behavior could be that setting both CSS properties at once triggers an immediate repaint of the page whereas setting them separately doesn't.

Some Javascript commands are known to cause a repaint. Getting the offsetHeight of an element is the one mentioned most often (see this post). As a matter of fact, it was used in this article to solve a problem with CSS transitions quite similar to the one presented here. And if we test that method by getting the element height between the transitions, we see that the resulting behavior is indeed correct:

$('.marquee-wrapper').css("transition", "none");
$('.marquee-wrapper').css("transform", "translateY(100px)");
$('.marquee-wrapper').height(); // Force a repaint
$('.marquee-wrapper').css("transition", "transform 3s linear");
$('.marquee-wrapper').css("transform", "translateY(-" + $(".marquee-content").outerHeight(true) + "px)");

if ($(".marquee").height() < $(".marquee-content").outerHeight(true)) {
    $(".marquee-content").clone().appendTo($(".marquee-wrapper"));
}
$('.marquee-wrapper').css("transition", "transform 3s linear");
$('.marquee-wrapper').css("transform", "translateY(-" + $(".marquee-content").outerHeight(true) + "px)");

setInterval(function () {
    $('.marquee-wrapper').css("transition", "none");
    $('.marquee-wrapper').css("transform", "translateY(100px)");
    $('.marquee-wrapper').height(); // Force a repaint
    $('.marquee-wrapper').css("transition", "transform 3s linear");
    $('.marquee-wrapper').css("transform", "translateY(-" + $(".marquee-content").outerHeight(true) + "px)");
}, 3000);
.marquee {
  margin: auto;
  width: 600px;
  height: 200px;
  overflow: auto;
}

.marquee-wrapper {
  transform: translateY(0);
}

.marquee-content {
  margin: 0;
  padding: 30px 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<section class="marquee">
  <div class="marquee-wrapper">
    <div class="marquee-content">
      Updates: Update (8 Mar 2016): Now plugin have new option: startVisible The marquee will be visible in the start if set to true. Thanks to @nuke-ellington 👠Update (24 Jan 2014): Note: people who been asking me how to use this plugin with content being
      loaded with Ajax, please read notes about this update. New methods added, so now after you start the plugin using var $mq = $('.marquee').marquee();, you start the plugin using var $mq = $('.marquee').marquee();, you start the plugin using var $mq
      = $('.marquee').marquee();, then you can pause, resume, togglepause, resume) and desestroy destroy toggle(pause, resume) and destroy toggle(pause, resume) and destroy methods e.g to remove the marquee plugin from your element simply use $mq.marquee('destroy');.
      Similarly you can use pause the marquee any time using $mq.marquee('pause');.
    </div>
  </div>
</section>
Community
  • 1
  • 1
ConnorsFan
  • 70,558
  • 13
  • 122
  • 146
  • This is interesting... But what is the reason behind this? What is the difference between adding css properties simultaneously and one by one? – user31782 Mar 24 '17 at 20:47
  • @user31782 - I added more details to my answer. – ConnorsFan Mar 25 '17 at 03:16
  • Could you explain why in setting both transition and transform together cause a browser repaint? Also does triggering a browser repaint/reflow blocks non blocking asynchronous implementation of style rules by javascript(jquery?)? – user31782 Mar 29 '17 at 11:35
  • Have also into account that in the following operations within the `setTimeout(function(){` you might have the same issue. So for complex examples, chaining animations using `animate()` callback or using a library good in sequencing like secuence.js might be useful. – Evhz Mar 29 '17 at 11:47
  • 1
    @user31782 - I don't know why setting both CSS properties would cause a repaint. But I also don't see another explanation as to why doing it that way systematically "works". Intuitively, it should be at least as bad as setting the `transition` first and setting the `transform` after. Does anybody have a better explanation? – ConnorsFan Mar 29 '17 at 12:27
  • at the time you wrap up two executions in the `$().css()` properties injector, those executions are computed by jquery into the same operational stack. That way, the operation, which seems to be treat as a whole, generates a sorted result set applied in order by the browser. In the case I explained, the separated operations are packed in different stacks of execution, thats why there's the need of controlling the operational result by using timeouts or delays. – Evhz Mar 30 '17 at 11:56
2

I shall explain you why this happens.
I've been dealing with javascript animations, and there are many js libraries (focused on maths) having into account operational delays (I/O availability policies) and timeouts.

In your first code snippet, you have these operations:

$('.marquee-wrapper').css("transform", "translateY(100px)");
$('.marquee-wrapper').css("transition", "transform 3s linear");

Transform

Css Transform method uses matrix based operations which have a high computational cost. There are css-animation frameworks that do use the graphics processor unit (having matrix operators) which works several times faster to achieve smooth, real-time graphic operations.

Transition

Css Transition is another graphical operation, but does not modify the css entity with a pure [matrix op matrix] transformation, it uses a right 1-dimension operator, that means your css matrix is modified with an [matrix op array].

Then the linear mode you choose, applies linear interpolation (probably just a few integration operators) on the position of your element. It has low computational cost, which makes the whole transition operation still faster to compute.

This is a schema of the execution taking place in a sort of timeline:

transform         calculation-process
exec -----------------------------------------> applied
 |         |                            |        |
 |         |                            |        |
 |         |                            |        |
transition |     calculation-process    |        |
--------- exec ---------------------> applied --------- 

As jQuery on the top of javascript has non blocking code execution (for i/o depending functions, and unless you code it synch) which is a base of the asynchronous policy of javascript, allows the next operation to execute even before the precedent finishes.

Your fix with the timeout function, ensures that the transform operation is completed before running following code, but as limitation, it will work only for clients with similar computational speed than the current client's processor. (If you develop it in a pc, then it might fail in a smartphone)

Another solution I use in my code is to use the jquery callbacks. Have a look at the jquery animate() doc in which is shown:

.animate( properties [, duration ] [, easing ] [, complete ] )

In your example it would be something like:

$('.marquee-wrapper').animate({"transform": "translateY(100px)"}, function(){
    // this code runs after transform ends...
    $('.marquee-wrapper').css("transition", "transform 3s linear");
    $('.marquee-wrapper').css("transform", "translateY(-" + $(".marquee-content").outerHeight(true) + "px)");
});

I found many useful libs around to "seriously" play with animations. These are some of the libs I use:

d3.js
bounce.js
secuence.js
paper.js

I hope it helps.

Update
There is a good SO answer about animate and css transitions here.

Community
  • 1
  • 1
Evhz
  • 8,852
  • 9
  • 51
  • 69
  • So its because `transform` takes more time to get applied, `transition` takes lesser time and javascript(only jquery?) has async code execution(although code is parsed line by line the later parsed code could execute earlier). – user31782 Mar 29 '17 at 11:02
  • 2. As you are using `animate` as a workaround, wouldn't this use `400ms` default of jquery and change `translateY` smoothly. I needed a rapid change of translateY value. – user31782 Mar 29 '17 at 11:13
  • 3. Could you explain why in use CannonFan's answer setting both `transition` and `transform` together cause a browser repaint? – user31782 Mar 29 '17 at 11:17
  • 4. Also does triggering a browser repaint/reflow blocks non blocking implementation of style rules by javascript? – user31782 Mar 29 '17 at 11:22
  • Regarding comment 1, jquery does asynch the part of the code in which it losses control over execution that is hardware calculation and I/O interruptions. Regarding comment 2, in the cases I used it, smoothness was achieved, but it depends on the amount of pixel richness to be moved. regarding comment 3 and 4, those are out of the scope of my answer. – Evhz Mar 29 '17 at 11:30
  • Is the asynchronous nature of execution a feature of jquery only? If I use vanila javascript would I get the execution of code synced, that is the execution order be same as parsing order? – user31782 Mar 29 '17 at 11:33
  • as I understand, vanilla does also make use of client hardware and client I/O interruptions, so it might also happen. As a good new is that vanilla, could make a better use of their operational caching than jQuery and achieve better results with the same hardware and I/O, also the libs I pasted as good examples do a good work behind the DOM scenes. – Evhz Mar 29 '17 at 11:35
0

I think that there are some great answers here telling you why it does work, but if you want a more browser supported animation, use jQuery animations.

$wrap = $(".marquee-wrapper")
$con = $(".marquee-content");
cHeight = $con.outerHeight(true)

if ($(".marquee").height() < cHeight) {
  $con.clone().appendTo( $wrap );
}

function animate() {
  $wrap.animate({
    top: "-=" + cHeight
  }, 3000, "linear", function() { 
    $(this).css("top", "0");
    animate();
  });
}

animate();

//Cache values
$wrap = $(".marquee-wrapper")
$con = $(".marquee-content");
cHeight = $con.outerHeight(true)

if ($(".marquee").height() < cHeight) {
  $con.clone().appendTo( $wrap );
}


function animate() {
  $wrap.animate({
    top: "-=" + cHeight //minus height from the value of top
  },
  3000, // milisecs of animations length
  "linear", // type of animations
  function() { //function to run after animation is complete
    $(this).css("top", "0");
    animate();
  });
}

animate(); //Run function in the beginning
.marquee {
  margin: auto;
  width: 600px;
  height: 200px;
  overflow: auto;
  position: relative;
}

.marquee-wrapper {  position: absolute;  }

.marquee-content {
  margin: 0;
  padding: 30px 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<section class="marquee">
  <div class="marquee-wrapper">
    <div class="marquee-content">
      Updates: Update (8 Mar 2016): Now plugin have new option: startVisible The marquee will be visible in the start if set to true. Thanks to @nuke-ellington 👠Update (24 Jan 2014): Note: people who been asking me how to use this plugin with content being
      loaded with Ajax, please read notes about this update. New methods added, so now after you start the plugin using var $mq = $('.marquee').marquee();, you start the plugin using var $mq = $('.marquee').marquee();, you start the plugin using var $mq
      = $('.marquee').marquee();, then you can pause, resume, togglepause, resume) and desestroy destroy toggle(pause, resume) and destroy toggle(pause, resume) and destroy methods e.g to remove the marquee plugin from your element simply use $mq.marquee('destroy');.
      Similarly you can use pause the marquee any time using $mq.marquee('pause');.
    </div>
  </div>
</section>

JSfiddle

Chris Happy
  • 7,088
  • 2
  • 22
  • 49