https://jsfiddle.net/fnethLxm/10/
$(document).ready(function() {
parallaxAuto()
});
function parallaxAuto() {
var viewer = document.querySelector('.viewer.active'),
frame_count = 5,
offset_value = 500;
// init controller
var controller = new ScrollMagic.Controller({
globalSceneOptions: {
triggerHook: 0,
reverse: true
}
});
// build pinned scene
var 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);
}
$(".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);
}
};
You can see that on 1 and 2 slide plugin work out fine, but on slide 3 it does not work. and error "Cannot read property 'destroy' of null"
A few days longer I sit and I can not understand
how to fix this?
I see 2 issues:
you're setting the scene and the controller to null on every right/left click, and require re-init of it re-calling parallaxAuto;
every time you call parallaxAuto you re-bind the listeners.
I took the freedom to rewrite it for you so the listeners will be bound only once: https://jsfiddle.net/j6u6wp7x/. I just isolated the part in which you re-init the controller and the scene so you can call it at the end of the click without re-binding the events.
Related
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);
}
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()
})
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
I am writing a class for rotating cube, but every time i rotate it or zoom, i get an error "Cannot read property 'render' of undefined". What am i doing wrong? I guess something is wrong with the scopes. Here is my class:
myclass = function() {
this.camera = null;
this.scene = null;
this.renderer = null;
this.product = null;
this.init = function (container) {
this.scene = new THREE.Scene();
this.camera = createCamera();
this.product = createProduct();
this.scene.add(this.product);
this.createRenderer();
this.setControls();
container.appendChild(this.renderer.domElement);
this.animate();
};
function createCamera() {
var camera = new THREE.PerspectiveCamera(20, 300 / 400, 1, 10000);
camera.position.z = 1800;
return camera;
}
function createProduct() {
var geometry = new THREE.BoxGeometry(300, 200, 200);
var materials = ...;
var product = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials));
return product;
}
this.createRenderer = function () {
this.renderer = new THREE.WebGLRenderer({antialias: true});
this.renderer.setClearColor(0xffffff);
this.renderer.setSize(this.sceneWidth, this.sceneHeight);
};
this.setControls = function () {
this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
this.controls.addEventListener('change', this.render);
};
this.animate = function () {
requestAnimationFrame(this.animate.bind(this));
this.render();
};
this.render = function () {
this.renderer.render(this.scene, this.camera);
};
};
Tnanks.
You have a scoping problem in the callback specified here:
this.controls.addEventListener( 'change', this.render );
In your class, add
var scope = this;
Then rewrite your render() method like so:
this.render = function () {
scope.renderer.render( scope.scene, scope.camera );
};
Also, the point of adding the event listener is to remove the animation loop.
So..., remove it, and only render when the mouse causes the camera to move.
You will also have to call render() once initially, and after models load.
three.js r.69
You can use bind method as follows it will hold the scope for the render method
this.controls.addEventListener( 'change', this.render.bind(this) );
I'm new to kinetic and I don't know about performance issues.
I made this example, you just have to click on the black and white image and drag over it, then, a colored circle apears.
The performance in chrome, safari on an Ipad, and even Opera Mobile on an android phone is quite good. In firefox it starts ok, but if you move the mouse for a while it slows down and doesn't work properly. The firebug profiler doesn't help a lot... How could I debug this issue in a better way?
In the drawing function there's an inner method onMove to do the hard work. I believe here lies the performance problem but I don't know how to achieve the same effect in a better way.
Any ideas?
function draw(images) {
var stage = new Kinetic.Stage({
container : 'container',
width : 1024,
height : 483
}), bn_layer = new Kinetic.Layer(), color_layer = new Kinetic.Layer(), circle_layer = new Kinetic.Layer(), bn = new Kinetic.Image({
x : 0,
y : 0,
image : images.bn,
width : 1024,
heigth : 483
}), tmp_circle = null, movable = false;
bn_layer.add(bn);
tmp_circle = addCircle(circle_layer, images);
var onMove = function() {
if (movable) {
var pos = getMousePosition(stage);
circle_layer.draw();
tmp_circle.remove();
tmp_circle.setPosition(pos.x, pos.y)
tmp_circle.setFillPatternImage(images.color);
tmp_circle.setFillPatternOffset(pos.x, pos.y);
circle_layer.add(tmp_circle);
}
}
stage.on("mousemove touchmove", onMove);
stage.on("mousedown touchstart", function() {
debug("activo")
circle_layer.show();
movable = true;
onMove();
circle_layer.draw();
});
stage.on("mouseup touchend", function() {
debug("inactivo")
circle_layer.draw();
tmp_circle.remove();
circle_layer.hide();
movable = false;
})
//stage.add(color_layer);
stage.add(bn_layer);
stage.add(circle_layer);
circle_layer.hide();
}
Update: Changing the mouse event for a requestAnimationFrame method controlled with a flag the performance improves a lot in firefox on windows. In firefox on Linux the performance is still crappy.
I think this might have some relation with what is commented in this topic:
Poor Canvas2D performance with Firefox on Linux
There they are talking about a possible bug in firefox related to the cairo libraries:
http://blog.mozilla.org/joe/2011/04/26/introducing-the-azure-project/
https://bugzilla.mozilla.org/show_bug.cgi?id=781731
Updated code
function Anim(layer, funcion){
var run = false;
var callback = funcion;
this.layer = layer;
function animate(){
callback();
if (!run){
return;
}
requestAnimFrame(function(){
animate();
})
};
this.start = function(){
run = true;
animate();
};
this.stop = function(){
run = false;
};
}
//draw on frames
function drawAnim(images){
var stage = new Kinetic.Stage({
container : 'container',
width : 1024,
height : 483
}), bn_layer = new Kinetic.Layer(),
hitareas_layer = new Kinetic.Layer(),
circle_layer = new Kinetic.Layer(),
bn = createImage(images.bn),
tmp_circle = null,
movable = false,
hit_areas = null,
c = 0,
colorArea = function() {
if (movable) {
var pos = getMousePosition(stage);
debug("posicion: "+pos.x+" "+pos.y+" " +c+ " " +tmp_circle.getX()+ " "+tmp_circle.getY());
if(pos.x !== tmp_circle.getX() || pos.y !== tmp_circle.getY()){
c++;
circle_layer.draw();
tmp_circle.remove();
tmp_circle.setPosition(pos.x, pos.y);
tmp_circle.setFillPatternImage(images.color);
tmp_circle.setFillPatternOffset(pos.x, pos.y);
circle_layer.add(tmp_circle);
}
}
},
anim = new Anim(null, function(){
colorArea();
}),
onPress = function() {
circle_layer.show();
//hitareas_layer.hide()
movable = true;
colorArea();
circle_layer.draw();
anim.start();
}, onRelease = function() {
anim.stop();
circle_layer.draw();
tmp_circle.remove();
circle_layer.hide();
//hitareas_layer.show()
movable = false;
c=0;
};
//hit_areas = setHitAreas(bn_layer);
bn_layer.add(bn);
tmp_circle = addCircle(100, {
x : 50,
y : 50
});
hit_areas = setHitAreas(hitareas_layer, images.color);
bn_layer.on(HitArea.HITTED, function(){
console.log("this");
})
//descomentar si queremos efecto al mover el rat�n
//stage.on("mousemove touchmove", colorArea);
stage.on("mousedown touchstart", onPress);
stage.on("mouseup touchend", onRelease);
stage.add(bn_layer);
stage.add(circle_layer);
stage.add(hitareas_layer);
circle_layer.hide();
}
Place if (movable) condition outside of onMove() function, this way you`ll not check every time for this capability:
if (movable) {
var onMove = function() {
var pos = getMousePosition(stage);
circle_layer.draw();
tmp_circle.remove();
tmp_circle.setPosition(pos.x, pos.y)
tmp_circle.setFillPatternImage(images.color);
tmp_circle.setFillPatternOffset(pos.x, pos.y);
circle_layer.add(tmp_circle);
}
}