So I've created a smooth scroll effect on my website, works nice, but I'm having an issue with Scrollmagic. When scrolling down, I'm translating elements, but it stutters. A lot. When I disable my smooth scroll script, everything works fine again.
BTW : I'm using Webpack and GSAP for the animations.
My guess is Scrollmagic isn't aware of the animation so it's using the end value, not the current one. But I can't find out how to fix this
Here is my smooth scroll :
import { TweenLite } from 'gsap';
const html = document.documentElement;
const body = document.body;
const scroller = {
target: document.querySelector('.scroll-container'),
ease: 0.1, // <= scroll speed
endY: 0,
y: 0,
resizeRequest: 1,
scrollRequest: 0,
};
let requestId = null;
TweenLite.set(scroller.target, {
rotation: 0.01,
force3D: true,
});
window.addEventListener('load', onLoad);
function onLoad() {
updateScroller();
window.focus();
window.addEventListener('resize', onResize);
document.addEventListener('scroll', onScroll);
}
function updateScroller() {
const resized = scroller.resizeRequest > 0;
if (resized) {
const height = scroller.target.clientHeight;
body.style.height = `${height}px`;
scroller.resizeRequest = 0;
}
const scrollY = window.pageYOffset || html.scrollTop || body.scrollTop || 0;
scroller.endY = scrollY;
scroller.y += (scrollY - scroller.y) * scroller.ease;
if (Math.abs(scrollY - scroller.y) < 0.05 || resized) {
scroller.y = scrollY;
scroller.scrollRequest = 0;
}
TweenLite.set(scroller.target, {
y: -scroller.y,
});
requestId = scroller.scrollRequest > 0 ? requestAnimationFrame(updateScroller) : null;
}
function onScroll() {
scroller.scrollRequest += 1;
if (!requestId) {
requestId = requestAnimationFrame(updateScroller);
}
}
function onResize() {
scroller.resizeRequest += 1;
if (!requestId) {
requestId = requestAnimationFrame(updateScroller);
}
}
And the Scrollmagic part :
import $ from 'jquery';
import * as ScrollMagic from 'scrollmagic';
import { TweenMax, TimelineMax, Power0 } from 'gsap';
import { ScrollMagicPluginGsap } from 'scrollmagic-plugin-gsap';
ScrollMagicPluginGsap(ScrollMagic, TweenMax, TimelineMax);
const controller = new ScrollMagic.Controller();
$('.big-outline-text').each(function() {
const tl = new TimelineMax();
const child = $(this);
if ($(this).hasClass('right-to-left')) {
tl.to(child, 2, { x: -300, ease: Power0.easeInOut });
} else if ($(this).hasClass('left-to-right')) {
tl.fromTo(child, 2, { x: -300 }, { x: 0, ease: Power0.easeInOut }, '+=1');
}
const scene = new ScrollMagic.Scene({
triggerElement: this,
triggerHook: 0.9,
duration: '110%',
})
.setTween(tl)
.addTo(controller);
});
$('.bottom-to-top').each(function() {
const tl2 = new TimelineMax();
const child = $(this);
if ($(this).hasClass('bottom-to-top')) {
tl2.fromTo(child, 2, { y: -300 }, { y: 100, ease: Power0.easeInOut });
}
const scene = new ScrollMagic.Scene({
triggerElement: this,
triggerHook: 0.9,
duration: '220%',
})
.setTween(tl2)
.addTo(controller);
});
I'm sure i'm not the first one having this problem, but i couldn't find any answer.
I managed to solve my issue with a refresh function for the scrollbar. Like in this codepen https://codepen.io/emraher/pen/GPRJEZ?editors=1010
They set the scrollbar and the scrollmagic scene as vars, and then this little gem
var elem = document.querySelector(".content");
var scrollbar = Scrollbar.init(elem)
scrollbar.addListener(() => {
scene.refresh()
})
Related
I'm working on a small flappy-bird-like-game demo. Everthing seems fine, but I have a small problem/question.
I setup a collider function, and the callback works as expected, when the "two" objects collide, but there is a strange behavior:
the white-square (the bird) can fly through the obstacles, when coming from the side
but cannot passthrough when coming from below or on above
BUT the callback is execute always.
blue arrow marks where the square passes through
green arrows mark where the square doesn't passthrough
I'm not sure if this is, because I'm using rectangles (they sometimes) cause problems, or because of my nested physics setup. I even tried to replaced the white rectangel with a sprite, but this still produces the same result/error.
For my demo: I could probablly just destroy the object and restart the level on collision, but I still would like to understand why this is happening? And how I can prevent this, inconsistant behavior.
I'm probably missing something, but couldn't find it, and I don't want to rebuild the application again.
So my question is: why is this happening? And How can I prevent this?
Here is the code:
const width = 400;
const height = 200;
const spacing = width / 4;
const levelSpeed = width / 4;
const flySpeed = width / 4;
var GameScene = {
create (){
let player = this.add.rectangle(width / 4, height / 2, 20, 20, 0xffffff);
this.physics.add.existing(player);
this.input.keyboard.on('keydown-SPACE', (event) => {
if(player.body.velocity.y >= -flySpeed/2){
player.body.setVelocityY(-flySpeed);
}
});
player.body.onWorldBounds = true;
player.body.setCollideWorldBounds(true );
this.physics.world.on("worldbounds", function (body) {
console.info('GAME OVER');
player.y = height / 2;
player.body.setVelocity(0);
});
this.pipes = [];
for(let idx = 1; idx <= 10; idx++) {
let obstacle = this.createObstacle(spacing * idx, Phaser.Math.Between(-height/3, 0));
this.add.existing(obstacle);
this.pipes.push(obstacle);
this.physics.add.collider(obstacle.list[0], player)
this.physics.add.collider(obstacle.list[1], player, _ => console.info(2))
}
},
update(){
this.pipes.forEach((item) => {
if(item.x <= 0){
item.body.x = spacing * 10;
}
})
},
extend: {
createObstacle (x, y){
let topPipe = (new Phaser.GameObjects.Rectangle(this, 0, 0 , 20 , height / 2 ,0xff0000)).setOrigin(0);
let bottomPipe = (new Phaser.GameObjects.Rectangle(this, 0, height/2 + 75, 20 , height / 2 ,0xff0000)).setOrigin(0);
this.physics.add.existing(topPipe);
this.physics.add.existing(bottomPipe);
topPipe.body.setImmovable(true);
topPipe.body.allowGravity = false;
bottomPipe.body.setImmovable(true);
bottomPipe.body.allowGravity = false;
let obstacle = new Phaser.GameObjects.Container(this, x, y, [
topPipe,
bottomPipe
]);
this.physics.add.existing(obstacle);
obstacle.body.velocity.x = - levelSpeed;
obstacle.body.allowGravity = false;
return obstacle;
}
}
};
var config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width,
height,
scene: [GameScene],
physics: {
default: 'arcade',
arcade: {
gravity: { y: flySpeed },
debug: true
},
}
};
var game = new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
Currently I just can assume, that the physics objects don't seem to work correct, when physics objects are nested.
Maybe I'm wrong, but since I rewrote the code again without nested physics - objects and it seems to work, I think my assumption Is correct. I shouldn't have tried to over engineer my code.
If someone has more insides, please let me know/share. I still not 100% sure, if this is the real reason, for the strange behavior.
Here the rewriten code:
const width = 400;
const height = 200;
const spacing = width / 4;
const levelSpeed = width / 4;
const flySpeed = width / 4;
var GameScene = {
create (){
let player = this.add.rectangle(width / 4, height / 2, 20, 20, 0xffffff);
this.physics.add.existing(player);
this.input.keyboard.on('keydown-SPACE', (event) => {
if(player.body.velocity.y >= -flySpeed/2){
player.body.setVelocityY(-flySpeed);
}
});
player.body.onWorldBounds = true;
player.body.setCollideWorldBounds(true );
this.physics.world.on("worldbounds", function (body) {
console.info('GAME OVER');
player.x = width / 4;
player.y = height / 2;
player.body.setVelocity(0);
});
this.pipes = [];
for(let idx = 1; idx <= 10; idx++) {
let obstacle = this.createObstacle(spacing * idx, Phaser.Math.Between(-height/3, 0));
this.add.existing(obstacle[0]);
this.add.existing(obstacle[1]);
this.pipes.push(...obstacle);
this.physics.add.collider(obstacle[0], player)
this.physics.add.collider(obstacle[1], player, _ => console.info(2))
}
},
update(){
this.pipes.forEach((item) => {
if(item.x <= 0){
item.body.x = spacing * 10;
}
item.body.velocity.x = - levelSpeed;
})
},
extend: {
createObstacle (x, y){
let topPipe = (new Phaser.GameObjects.Rectangle(this, x, -20 , 20 , height / 2 ,0xff0000)).setOrigin(0);
let bottomPipe = (new Phaser.GameObjects.Rectangle(this, x, height/2 + 75, 20 , height / 2 ,0xff0000)).setOrigin(0);
this.physics.add.existing(topPipe);
this.physics.add.existing(bottomPipe);
topPipe.body.setImmovable(true);
topPipe.body.allowGravity = false;
topPipe.body.velocity.x = - levelSpeed;
bottomPipe.body.setImmovable(true);
bottomPipe.body.allowGravity = false;
bottomPipe.body.velocity.x = - levelSpeed;
return [topPipe, bottomPipe];
}
}
};
var config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width,
height,
scene: [GameScene],
physics: {
default: 'arcade',
arcade: {
gravity: { y: flySpeed },
debug: true
},
}
};
var game = new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
I am trying to achieve a video animation with scrollmagic.
Desired outcome is that, the video plays based on the user scroll position.
export default function magic() {
//MAIN
var controller = new ScrollMagic.Controller();
// SceneOne animation
const $video = $('#soVideo');
let sceneOne = new ScrollMagic.Scene({
duration: 9000,
triggerElement: '#trigger1',
triggerHook: 0
})
.setPin('#trigger1')
.addIndicators()
.addTo(controller);
// SceneOne animation
let accelamount = 0.1;
let scrollpos = 0;
let delay = 0;
sceneOne.on('update', e => {
scrollpos = e.scrollPos / 1000;
});
setInterval(() => {
delay += (scrollpos - delay) * accelamount;
$video.currentTime = delay;
console.log(delay);
}, 33.36);
}
The problem is, I cannot get it working, the video stays static at scroll.
I am trying to do this for a product page, not sure what am I doing wrong.
Thanks for any tips!
Ok figured it out. Works now:
export default function magic() {
const intro = document.querySelector(".video-section");
const video = intro.querySelector('.video_zero');
const text = intro.querySelector('.intro-text');
var controller = new ScrollMagic.Controller();
// SceneOne animation
let sceneOne = new ScrollMagic.Scene({
duration: 9500,
triggerElement: intro,
triggerHook: 0
})
.setPin(intro)
.addIndicators()
.addTo(controller);
// SceneOne animation
let accelamount = 0.1;
let scrollpos = 0;
let delay = 0;
sceneOne.on('update', e => {
scrollpos = e.scrollPos / 1000;
});
setInterval(() => {
delay += (scrollpos - delay) * accelamount;
video.currentTime = delay;
}, 33.3);
}
According to the Phaser 3 examples I've created a tilemap from JSON file and added firing bullets with a left mouse clicking in a cursor's direction. Also a platform was added. My problem is that there are no collisions detected between a bullet and a tilemap's objects. But collisions work fine between the bullet and a platform.
Here is my demo code:
var config = {
type: Phaser.CANVAS,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
physics: {
default: 'arcade',
arcade: {
gravity: { y: 300 },
debug: false
}
},
scene: {
preload: preload,
create: create
}
};
var game = new Phaser.Game(config);
var bullets;
var platforms;
function preload ()
{
this.load.tilemapTiledJSON('map', 'assets/tilemaps/maps/impact-tilemap.json');
this.load.image('kenney', 'assets/tilemaps/tiles/kenney.png');
this.load.image('ground', 'src/games/firstgame/assets/platform.png');
this.load.image('bullet', 'src/games/firstgame/assets/star.png');
}
function create ()
{
var map = this.make.tilemap({ key: 'map' });
var tileset = map.addTilesetImage('kenney');
var layer = map.createStaticLayer(0, tileset, 0, 0);
layer.setCollisionByExclusion([-1]);
this.physics.world.bounds.width = layer.width;
this.physics.world.bounds.height = layer.height;
platforms = this.physics.add.staticGroup();
platforms.create(400, 268, 'ground');
// Fires bullet on left click of mouse
this.input.on('pointerdown', function() {
fire(this);
}, this);
var Bullet = new Phaser.Class({
Extends: Phaser.GameObjects.Image,
initialize: function Bullet(scene)
{
Phaser.GameObjects.Image.call(this, scene, 0, 0, 'bullet');
this.speed = Phaser.Math.GetSpeed(300, 1);
this.velocity = new Phaser.Geom.Point(0, 0);
},
fire: function (x, y, direction)
{
this.setPosition(x, y);
this.setActive(true);
this.setVisible(true);
this.velocity.setTo(0, -this.speed);
Phaser.Math.Rotate(this.velocity, direction);
},
update: function (time, delta)
{
// Update position based on velocity
this.x += this.velocity.x * delta;
this.y += this.velocity.y * delta;
}
});
bullets = this.physics.add.group({
classType: Bullet,
maxSize: 30,
runChildUpdate: true
});
this.physics.add.collider(bullets, platforms, callbackFunc, null, this);
this.physics.add.collider(bullets, layer, callbackFunc, null, this);
}
function callbackFunc(bullet, target)
{
if ( bullet.active === true ) {
console.log("Hit!");
bullet.setActive(false);
bullet.setVisible(false);
}
}
function fire(that)
{
var bullet = bullets.get();
if (bullet) {
bullet.body.allowGravity = false;
var angle = Math.atan2(that.input.activePointer.y - 400, that.input.activePointer.x - 300);
bullet.fire(300, 400, angle + (3.14/2));
}
}
You can copy-paste it to any example at https://labs.phaser.io and start clicking to fire at any direction.
I've tried to add a standard player from examples too and collisions work fine with the tilemap's objects. So the problem is with bullets only.
Please help to solve this. Thanks!
How to reset values when switching slides?
https://jsfiddle.net/j6u6wp7x/
var scene;
var controller;
$(document).ready(function() {
parallaxAuto();
$(".right_arrr").click(function(){
var block = $(this).siblings('.secondSlider');
el = block.find(".active");
elNum = el.attr("data-num");
if(elNum < block.find('.slide').length) {
elNum++;
} else {
elNum=1;
}
hideShow(elNum, block);
alert('slide №' + elNum)
scene = scene.destroy(true);
scene = null;
controller.destroy(true);
controller = null;
parallaxAuto();
});
$(".left_arrr").click(function(){
var block = $(this).siblings('.secondSlider');
el = block.find(".active");
elNum = el.attr("data-num");
if(elNum > 1) {
elNum--;
} else {
elNum=block.find('.slide').length;
}
hideShow(elNum, block);
scene = scene.destroy(true);
scene = null;
controller.destroy(true);
controller = null;
parallaxAuto();
});
});
function hideShow(num, block) {
block.find("div.active").removeClass("active").animate({ opacity: 0,},300);
block.find("div.slide"+num).addClass("active").animate({ opacity: 1,},300);
}
// init variables
function parallaxAuto() {
var viewer = document.querySelector('.viewer.active'),
frame_count = 5,
offset_value = 500;
// init controller
controller = new ScrollMagic.Controller({
globalSceneOptions: {
triggerHook: 0,
reverse: true
}
});
// build pinned scene
scene = new ScrollMagic.Scene({
triggerElement: '#sticky',
duration: (frame_count * offset_value) + 'px',
reverse: true
})
.setPin('#sticky')
//.addIndicators()
.addTo(controller);
// build step frame scene
for (var i = 1, l = frame_count; i <= l; i++) {
new ScrollMagic.Scene({
triggerElement: '#sticky',
offset: i * offset_value
})
.setClassToggle(viewer, 'frame' + i)
//.addIndicators()
.addTo(controller);
}
}
You can see in the example that the transition to the next slide stored value
And it is necessary that when we switch the values updated. I can not achieve this effect
Does anyone have any ideas why this is not working? Seems to hit the first background but then does not change position.
// Avatar animations
var fadePulse = true; // true: will fade avatar images and pulse in and out once changed
// false: will slide the avatars in and out
var avatarCount = 5; // set the amount of avatars in the sprite image
var avatarSpeed = 2 // set the avatar transition speed
var avatarHeight = 250; // set the height of a single avatar image
var avatars = creative.avatars;
var animDuration = 0.4;
var avatarCurrentIndex = 0;
var avatarAni = new TimelineLite({ paused: true, repeat: -1 });
function startAvatarAnimation() {
show(avatars);
avatarAni.to(avatars, animDuration, {
scaleX: 1, // 1.1 (for bouncing)
scaleY: 1, // 1.1 (for bouncing)
ease: Power3.easeIn,
onComplete: onCompleteScaleIn
});
avatarAni.to(avatars, animDuration, {
scaleX: 1,
scaleY: 1,
ease: Power3.easeOut
});
avatarAni.timeScale(avatarSpeed);
avatarAni.play();
}
function onCompleteScaleIn() {
avatarCurrentIndex ++;
console.log(avatarCurrentIndex ++);
if(avatarCurrentIndex <= avatarCount-1){
TweenLite.set(avatars, {
backgroundPosition: '0 -' + (avatarCurrentIndex * avatarHeight) + 'px'
});
} else {
avatarAni.pause();
hide(avatars);
}
}
It all seems to work apart from that part with looping through and adjusting the position.
// Avatar animations
var fadePulse = true; // true: will fade avatar images and pulse in and out once changed
// false: will slide the avatars in and out
var avatarCount = 5; // set the amount of avatars in the sprite image
var avatarSpeed = 2 // set the avatar transition speed
var avatarHeight = 250; // set the height of a single avatar image
var avatars = creative.avatars;
var animDuration = 0.4;
var avatarCurrentIndex = 0;
var i = 0;
var avatarAni = new TimelineMax({ paused: true, repeat: -1 });
function startAvatarAnimation() {
show(creative.avatars);
avatarAni.to(avatars, animDuration, { scaleX: 1, scaleY: 1, ease: Power3.easeIn, onComplete: onCompleteScaleIn });
avatarAni.to(avatars, animDuration, { scaleX: 1, scaleY: 1, ease: Power3.easeOut });
avatarAni.timeScale(avatarSpeed);
avatarAni.play();
}
function onCompleteScaleIn() {
i++;
if(i <= avatarCount-1){
TweenMax.set(avatars, { backgroundPosition: '0 -' + (i * avatarHeight) + 'px' });
} else {
avatarAni.pause();
hide(avatars);
}
}