5

I have a jQuery function changing the background-position property of three elements on mousemove and this seems to be causing some performance issues.

It should be noted that the background images of these elements are SVGs.

Example code:

$(window).on('mousemove', function(event) {
    window.requestAnimationFrame(function() {

        $banner.find('.pattern').each(function(key) {

            var modifier = 20 * (key + 1);

            $(this).css({
                'background-position': (event.pageX / modifier)+'px '+(event.pageY / modifier)+'px'
            });

        });

    });
});

See my working code here: https://codepen.io/thelevicole/project/full/DarVMY/

I am making use of window.requestAnimationFrame() and I also have will-change: background-position; css attribute on each element.

As you can probably tell, this effect is lagging. It seems to get worse on bigger window sizes.

I'm pretty sure the issue is caused by using SVGs for the background images instead of PNGs. The reason I am using SVGs is because of high pixel density screens.

If anyone can give some insight on how I can improve the FPS without having to use PNGs that would be great. Thanks.

4
  • Have you tried using your svg's as inline elements instead of background-image and then moving them using transforms? Commented Sep 28, 2017 at 15:10
  • Can't you invoke the find method outside the event function? I think you can calculate it only once in the outer scope and reuse the reference: $patterns = $banner.find('.pattern'); $(window).on('mousemove', function(event) { window.requestAnimationFrame(function() { $patterns.each(function(key) ... Commented Sep 28, 2017 at 15:14
  • You can also get rid of JQuery and use the appropriate javascript DOM methods, it will give you further performance Commented Sep 28, 2017 at 15:18
  • Four things a browser can animate cheaply: translate, scale, rotate, and opacity. Can you rewrite your JS to use transform: translate() instead of repositioning backgrounds? Commented Sep 28, 2017 at 15:24

2 Answers 2

1

Success. My solution has been a combination of suggestions.

I am now changing the transform property of each element but I ran into another issue while doing this. I have a transform keyframe animation on those same elements and the JS applied styles were being ignored.

To fix this I nested the the keyframe animation elements and used JS to transform the parent.

I have applied the advice from @CristianTraìna to move window.requestAnimationFrame() outside of my mousemove

You can see the update at my original link: https://codepen.io/thelevicole/project/full/DarVMY/

Sadly CodePen doesn't allow for versioning on projects.


Final working code

(function($) {
	'use strict';
	
	var $banner = $('section.interactive');
	if ($banner.length) {
		var $patterns = $banner.find('.pattern');
		
		var x = 0,
			y = 0;
		
		// Bind animation to cursor
		$(window).on('mousemove', function(event) {
			x = event.pageX;
			y = event.pageY;
		});
		
		/**
		 * Tell the browser that we wish to perform an animation
		 * @see https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
		 */
		window.requestAnimationFrame(function animation() {

			// Loop each pattern layer
			$patterns.each(function(key) {

				// Modify the x,y coords per element to give "depth"
				var modifier = 20 * (key + 1);

				// Move background position
				$(this).css({
					'transform': 'translate('+(x / modifier)+'px, '+(y / modifier)+'px)'
				});

			});
			
			window.requestAnimationFrame(animation);

		});
		
		
	}
	
})(jQuery);
section.interactive {
  position: relative;
  height: 100vh;
  background-image: linear-gradient(45deg, #6ac8ea 0%, #5de2c1 100%);
}

section.interactive .layers {
  overflow: hidden;
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.15) 0%, transparent 40%, transparent 60%, rgba(0, 0, 0, 0.1) 100%);
}

section.interactive .layers .pattern {
  position: absolute;
  top: -10px;
  left: -10px;
  bottom: -10px;
  right: -10px;
  background-position: top left;
  will-change: background-position;
  background-size: 1000px 1000px;
}

section.interactive .layers .pattern .inner {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
}

section.interactive .layers .pattern.pattern-1 .inner {
  background-image: url("http://www.inrialpes.fr/sed/people/boissieux/VEAD/out_Stars.svg");
  filter: blur(2px);
  opacity: 0.3;
  z-index: 1;
  animation: Floating 10s infinite;
  animation-delay: 2s;
  background-size: 800px 800px;
}

section.interactive .layers .pattern.pattern-2 .inner {
  background-image: url("http://www.inrialpes.fr/sed/people/boissieux/VEAD/out_Stars.svg");
  filter: blur(1px);
  opacity: 0.4;
  z-index: 2;
  animation: Floating 10s infinite;
  animation-delay: 1s;
  background-size: 900px 900px;
}

section.interactive .layers .pattern.pattern-3 .inner {
  background-image: url("http://www.inrialpes.fr/sed/people/boissieux/VEAD/out_Stars.svg");
  opacity: 0.4;
  z-index: 3;
  animation: Floating 10s infinite;
  background-size: 1000px 1000px;
}

@keyframes Floating {
  0% {
    transform: translate(-10px, 10px);
  }
  50% {
    transform: translate(10px, -10px);
  }
  100% {
    transform: translate(-10px, 10px);
  }
}
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<meta http-equiv="X-UA-Compatible" content="ie=edge">
	<title>Animation performance</title>
	<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css">
	<link rel="stylesheet" href="styles.processed.css">
</head>
<body>
	<section class="interactive">
		<div class="layers">
			<div class="pattern pattern-3">
				<div class="inner"></div>
			</div>
			<div class="pattern pattern-2">
				<div class="inner"></div>
			</div>
			<div class="pattern pattern-1">
				<div class="inner"></div>
			</div>
		</div>
	</section>
	<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</body>
</html>

Sign up to request clarification or add additional context in comments.

1 Comment

First of all, great that you found the answer! Could you include the code of your answer so future use don't rely on codepen to get it?
0

When you call the on method you are creating an asynchronous function that will listen to an event, when this event is fired you create another asynchronous function that will listen to an event.

Then, what you're doing is listening to an event (mousemove) and set another event (requestAnimationFrame) when the first is fired, but what if mousemove is fired twice in a short time and requestAnimationFrame still didn't execute its callback the first time?

A cleaner way to solve the problem is this:

var x = 0;
var y = 0;
$patterns = $banner.find('.pattern');
$(window).on('mousemove', function(event) {
  x = event.pageX;
  y = event.pageY;
});

window.requestAnimationFrame(function moveBackground() {
  $patterns.each(function(key) {
    var modifier = 20 * (key + 1);
    $(this).css({
      'background-position': (x / modifier)+'px '+(y / modifier)+'px'
    });
  });
  window.requestAnimationFrame(moveBackground);
});

You can see my working project (without JQuery) clicking here

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.