teejay_hh Posted June 29, 2020 Share Posted June 29, 2020 Hello I have a question performance. I wrote small horizontal slider which also moves 2 background images. The following code shows my slider implementation. I wrote this for my team so there are few more comments which dont assume gsap knowledge. define(function (require) { const gsap = require('gsap/gsap.min').gsap const Draggable = require('gsap/Draggable.min').Draggable const InertiaPlugin = require('gsap/InertiaPlugin.min').InertiaPlugin gsap.registerPlugin(Draggable, InertiaPlugin) /** * The slider class creates a draggable slider which uses gsap's draggable plugin * The slider requires a certain dom structure to function and therefore is not reusable * The class requires `gsap` as wells as the 2 plugins `Draggable, InertiaPlugin` to be registered * * The draggable will start an auto rotation by default, which can be interrupted via interacting with it. * * @class Slider * * * */ return class Slider { constructor(delay = 8, endCallback = () => {}) { this.slideDelay = delay this.slideDuration = 1 this.slider = document.getElementById('slider') this.slides = document.querySelectorAll('.slide') this.slidesInner = document.querySelectorAll('.slides-inner') this.grass = document.getElementById('bg-three') this.hills = document.getElementById('bg-two') this.clouds = document.getElementById('bg-one') this.numSlides = this.slides.length this.snapX = 0 this.animationCounter = 1 this.slideWidth = this.slides[0].offsetWidth this.endCallback = endCallback this.hillsXSetter = gsap.quickSetter(this.hills, 'css') this.cloudsXSetter = gsap.quickSetter(this.clouds, 'css') this.createResizeListener() this.createDraggable() this.initiateSliderAnimation() } /** * Creates the draggable component with a few defaults. Please check out the * documentation if you would like to know more about what this plugin can do * * @see https://greensock.com/docs/v3/Plugins/Draggable */ createDraggable() { this.draggable = new Draggable(this.slidesInner, { type: 'x', // only horizontal scrolling bounds: this.slider, // bounds the draggable to the container so it cant be dragged out of it inertia: true, // momentum - throwing behaviour (magic) edgeResistance: 0.8, dragResistance: 0.3, throwProps: true, onDragStart: this.killAnimation, // callback which gets executed on every tick of dragging onDrag: this.onDrag, // callback which gets executed on every tick of dragging onThrowUpdate: this.onDrag, // callback which gets executed when the draggable is thrown snap: this.snapX, // is set to a function which calculates a snapping position callbackScope: this, // sets the callback scope to be the class instead of the draggable instance // implement some cool live snapping }) } /** * We start a delayed call which initiated the animation cycle to run through all slides until the end * This will set a timer variable which can be used to either kill the call or to restart it. */ initiateSliderAnimation() { this.timer = gsap.delayedCall(this.slideDelay, this.autoPlay, null, this) } /** * Stop the animation loop */ killAnimation() { if (this.timer) this.timer.kill() } /** * The method get called by the delayed call `initiateSliderAnimation` which starts the slide animation * When the draggable is pressed, dragged or thrown at the time of execution then we will kill the next call. * We also stop at the end of the last slide which executes a callback which is used to redirect to a different page. */ autoPlay() { if (this.animationCounter >= this.numSlides) { this.killAnimation() this.endCallback() return } if (this.draggable.isPressed || this.draggable.isDragging || this.draggable.isThrowing) { this.killAnimation() } else { this.animateSlides() } } /** * The animateSlides method runs an animation which slides the draggable one screen further * If the function is called multiple times it will progress through all slides sequentially * We use the timer variable to create a loop which calls the delayed call from `initiateSliderAnimation` method * again. */ animateSlides() { const _this = this // callback functions require us to use a scope var _this.animationCounter++ _this.timer.restart(true) // create a loop // slides the draggable one screen further gsap.to(_this.slidesInner, { x: '-=' + _this.slideWidth, duration: _this.slideDuration, ease: 'quad.inOut', onUpdate: function (s) { const drag = _this.draggable.update() _this.onDrag(null, drag.x) }, }) } /** * This method is called when the we animate or drag the draggable. Its used to create the paralax effect * on the different backgrounds. * * @param pointer * @param s */ onDrag(pointer = null, s = null) { if (s === null) s = this.draggable.x // quicksetter (defined in the constructor) have a massive performance boost // to the normal set method at least 50% - https://greensock.com/docs/v3/GSAP/gsap.quickSetter() this.hillsXSetter({ x: s / 5 }) this.cloudsXSetter({ x: s / 8 }) } /** * The resizeListener listens to the window resize event and sets the Draggable width programmatically */ createResizeListener() { window.addEventListener('resize', this.setDraggableWidth.bind(this)) this.setDraggableWidth() } /** * We use the window width set the slide width to stretch the whole screen * The slides are setup as flex colums of 1, which means they will adjust to the size of its parent container. * To make these slides fullscreen we need to set the container the size of x times the screen width. * We also adjust the snaping based on the current screen width. */ setDraggableWidth() { this.slideWidth = document.documentElement.clientWidth || document.body.clientWidth gsap.set(this.slidesInner, { width: this.numSlides * this.slideWidth + 'px' }) gsap.set(this.slides, { width: 100 / this.numSlides + '%' }) // make sure we show the inner container after the slides are repositioned. gsap.to(this.slidesInner, { delay: 0.5, duration: 0.5, autoAlpha: 1 }) this.snapX = function (endValue) { const slideWidth = document.body.offsetWidth return Math.round(endValue / slideWidth) * slideWidth } if (this.draggable) this.draggable.update(true) } } }) Its fairly straight forward and pretty pretty fast on my mbpro but thats not the measure. (I am not sure I can create a pen with the business plugins and I would have to change all assets (I guess placeholder would work)) Unfortunately this is not running smooth (like 60fps smooth) on an ipad of the 7th gen which I consider pretty fast (A10 Fusion chip). When I take out the dragging x setter (no paralax effect) everything is really nice, but moving the images is the problem. What steps can I take to optimize it? Perhaps: - dont resize images, keep them in their original size (no with 100% stretch)? - try different way of setting the x pos on those images ? - reduce size of images (remove transparent portion) - I have implemented the paralax effect wrong ? Please let me know if you have any tips. Cheers Thomas Link to comment Share on other sites More sharing options...
teejay_hh Posted June 29, 2020 Author Share Posted June 29, 2020 I replaced the - quickSetter with normal to tweens -> gsap.to(this.hills, {duration: 0.1, x: s/5 }) gsap.to(this.clouds, {duration: 0.1, x: s/8 }) which seems to make a big difference. Not perfect but closer to what I want. Link to comment Share on other sites More sharing options...
OSUblake Posted June 29, 2020 Share Posted June 29, 2020 Hard to say what if any performance problems there might be without seeing a demo. The quickSetter just set values, so maybe you are mistaking that for performance problems. 🤷♂️ If you need a smooth transition, then you can interpolate changes like in this demo. See the Pen WNNNBpo by GreenSock (@GreenSock) on CodePen And the most performant way to use quickSetter is to specify the property. this.hillsXSetter = gsap.quickSetter(this.hills, 'x', 'px') this.hillsXSetter(s / 5) Also, setting will-change: transform in your css for the images can improve performance. 4 Link to comment Share on other sites More sharing options...
teejay_hh Posted June 29, 2020 Author Share Posted June 29, 2020 Wow those 2 tweak made this run without any studder at 60 frames, thanks so much. I used will-change in the past but I couldn't remember it anymore. 1 Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now