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