i'm using the vis-timeline library. In the documentations there is no property for easing the touch pan-x when navigating the timeline, resulting in a stiff and rigid navigation.
I tried this:
timeline.on('rangechange', function(properties) {
var windowStart = timeline.getWindow().start;
var windowEnd = timeline.getWindow().end;
var distance = properties.event.deltaX * properties.event.velocityX;
var newWindowStart = windowStart.valueOf() - distance;
var newWindowEnd = windowEnd.valueOf() - distance;
timeline.setWindow(newWindowStart, newWindowEnd, {
animation: {
easingFunction: 'easeInOutQuad'
}
});
});
How can i implement an eased panX navigation?
Ok, i find a solution.
Here's my code:
timeline.on("rangechanged", (properties) => {
if (properties.event) {
let distance =
properties.event.deltaX *
Math.abs(properties.event.velocityX);
let newWindowStart = new Date(
properties.start.valueOf() - distance * 100000000
);
let newWindowEnd = new Date(
properties.end.valueOf() - distance * 100000000
);
timeline.setWindow(
newWindowStart,
newWindowEnd,
{
animation: {
duration: 500,
easingFunction: "easeOutQuad",
},
}
);
}
Related
I have a loop of scenes with videos that play on scroll (loop because it's integrated with WordPress ACF Flexible fields). I'm using ScrollMagic for that (and GSAP but that doesn't apply here).
What I'm trying to do is to make the duration of the whole scene same as duration of the video of the scene (on the front-end there'll be a presentation with different videos). I tried simply video.duration, but it beahvaes in a weird way: sometimes it works, sometimes not. Is there a solution for this?
Here is my code:
const startVideoFullScreenOnScrollFunction = () => {
const startVideoFullScreenOnScroll = document.querySelectorAll('.video-fullscreen-text-on-scroll');
for (let i = 0; i < startVideoFullScreenOnScroll.length; i += 1) {
if (typeof (startVideoFullScreenOnScroll[i]) !== 'undefined' && startVideoFullScreenOnScroll[i] != null) {
const controllerVideoFullScreenOnScroll = new ScrollMagic.Controller();
const videoFullScreen = startVideoFullScreenOnScroll[i].querySelector('video');
const textsOnVideo = startVideoFullScreenOnScroll[i].querySelectorAll('.video-fullscreen-text-on-scroll__copy');
const timelineVideoFullScreenOnScroll = new TimelineMax();
// fade in & fade out text
for (let j = 0; j < textsOnVideo.length; j += 1) {
timelineVideoFullScreenOnScroll
.to(textsOnVideo[j], {
opacity: 1,
duration: 3,
})
.to(textsOnVideo[j], {
opacity: 0,
duration: 3,
});
}
// ScrollMagic scene
let sceneVideoFullScreenOnScroll = new ScrollMagic.Scene({
duration: ???,
triggerElement: startVideoFullScreenOnScroll[i],
triggerHook: 0
})
.setTween(timelineVideoFullScreenOnScroll)
.setPin(startVideoFullScreenOnScroll[i])
.addTo(controllerVideoFullScreenOnScroll);
let scrollpos = 0;
let startpos = 0;
sceneVideoFullScreenOnScroll.on('update', e => {
startpos = e.startPos / 1000;
scrollpos = e.scrollPos / 1000;
if (scrollpos >= startpos) {
videoFullScreen.currentTime = scrollpos - startpos;
};
});
}
}
}
I coded a little simulation where you can dig cubes, they fall an stack, you can consume some or make a selection explode.
I started this little project for fun and my aim is to handle as much cubes as possible (100k would be a good start).
The project is simple, 3 possibles actions:
Dig cubes (2k each click)
Consume cubes (50 each click)
Explode cubes (click on two points to form a rectangle)
Currently, on my computer, performance starts to drop when I got about 20k cubes. When you select a large portion of cubes to explode, performance are heavily slowed down too. I'm not sure the way I simplified physics is the best way to go.
Could you give me some hints on how to improve/optimize it ?
Here is the complete code : (The stacking doesn't work in the SO snippet so here is the codepen version)
(() => {
// Variables init
let defaultState = {
cubesPerDig : 2000,
cubesIncome : 0,
total : 0
};
let cubeSize = 2, dropSize = window.innerWidth, downSpeed = 5;
let state,
digButton, // Button to manually dig
gameState, // An object containing the state of the game
cubes, // Array containing all the spawned cubes
heightIndex, // fake physics
cubesPerX, // fake physics helper
playScene; // The gamescene
// App setup
let app = new PIXI.Application();
app.renderer.view.style.position = "absolute";
app.renderer.view.style.display = "block";
app.renderer.autoResize = true;
document.body.appendChild(app.view);
// Resize
function resize() {
app.renderer.resize(window.innerWidth, window.innerHeight);
}
window.onresize = resize;
resize();
// Hello ! we can talk in the chat.txt file
// Issue : When there are more than ~10k cubes, performance start to drop
// To test, click the "mine" button about 5-10 times
// Main state
function play(delta){
// Animate the cubes according to their states
let cube;
for(let c in cubes){
cube = cubes[c];
switch(cube.state) {
case STATE.LANDING:
// fake physics
if(!cube.landed){
if (cube.y < heightIndex[cube.x]) {
cube.y+= downSpeed;
}else if (cube.y >= heightIndex[cube.x]) {
cube.y = heightIndex[cube.x];
cube.landed = 1;
heightIndex[cube.x] -= cubeSize;
}
}
break;
case STATE.CONSUMING:
if(cube.y > -cubeSize){
cube.y -= cube.speed;
}else{
removeCube(c);
}
break;
case STATE.EXPLODING:
if(boundings(c)){
continue;
}
cube.x += cube.eDirX;
cube.y += cube.eDirY;
break;
}
}
updateUI();
}
// Game loop
function gameLoop(delta){
state(delta);
}
// Setup variables and gameState
function setup(){
state = play;
digButton = document.getElementById('dig');
digButton.addEventListener('click', mine);
playScene = new PIXI.Container();
gameState = defaultState;
/* User inputs */
// Mine
document.getElementById('consume').addEventListener('click', () => {consumeCubes(50)});
// Manual explode
let explodeOrigin = null
document.querySelector('canvas').addEventListener('click', e => {
if(!explodeOrigin){
explodeOrigin = {x: e.clientX, y: e.clientY};
}else{
explode(explodeOrigin, {x: e.clientX, y: e.clientY});
explodeOrigin = null;
}
});
window['explode'] = explode;
heightIndex = {};
cubesPerX = [];
// Todo fill with gameState.total cubes
cubes = [];
app.ticker.add(delta => gameLoop(delta));
app.stage.addChild(playScene);
}
/*
* UI
*/
function updateUI(){
document.getElementById('total').innerHTML = cubes.length;
}
/*
* Game logic
*/
// Add cube when user clicks
function mine(){
for(let i = 0; i < gameState.cubesPerDig; i++){
setTimeout(addCube, 5*i);
}
}
// Consume a number of cubes
function consumeCubes(nb){
let candidates = _.sampleSize(cubes.filter(c => !c.eDirX), Math.min(nb, cubes.length));
candidates = candidates.slice(0, nb);
candidates.map(c => {
dropCubes(c.x);
c.state = STATE.CONSUMING;
});
}
const STATE = {
LANDING: 0,
CONSUMING: 1,
EXPLODING: 2
}
// Add a cube
function addCube(){
let c = new cube(cubeSize);
let tres = dropSize / cubeSize / 2;
c.x = window.innerWidth / 2 + (_.random(-tres, tres) * cubeSize);
c.y = 0//-cubeSize;
c.speed = _.random(5,8);
cubes.push(c);
c.landed = !1;
c.state = STATE.LANDING;
if(!cubesPerX[c.x]) cubesPerX[c.x] = [];
if (!heightIndex[c.x]) heightIndex[c.x] = window.innerHeight - cubeSize;
cubesPerX[c.x].push(c);
playScene.addChild(c);
}
// Remove a cube
function removeCube(c){
let cube = cubes[c];
playScene.removeChild(cube);
cubes.splice(c,1);
}
// Delete the cube if offscreen
function boundings(c){
let cube = cubes[c];
if(cube.x < 0 || cube.x + cubeSize > window.innerWidth || cube.y < 0 || cube.y > window.innerHeight)
{
removeCube(c);
return true;
}
}
// explode some cubes
function explode(origin, dest){
if(dest.x < origin.x){
dest = [origin, origin = dest][0]; // swap
}
var candidates = cubes.filter(c => c.state != STATE.EXPLODING && c.x >= origin.x && c.x <= dest.x && c.y >= origin.y && c.y <= dest.y);
if(!candidates.length)
return;
for(let i = origin.x; i <= dest.x; i++){
dropCubes(i);
}
candidates.forEach(c => {
c.explodingSpeed = _.random(5,6);
c.eDirX = _.random(-1,1,1) * c.explodingSpeed * c.speed;
c.eDirY = _.random(-1,1,1) * c.explodingSpeed * c.speed;
c.state = STATE.EXPLODING;
});
}
// Drop cubes
function dropCubes(x){
heightIndex[x] = window.innerHeight - cubeSize;
if(cubesPerX[x] && cubesPerX[x].length)
cubesPerX[x].forEach(c => {
if(c.state == STATE.EXPLODING) return;
c.landed = false; c.state = STATE.LANDING;
});
}
/*
* Graphic display
*/
// Cube definition
function cube(size){
let graphic = new PIXI.Graphics();
graphic.beginFill(Math.random() * 0xFFFFFF);
graphic.drawRect(0, 0, size, size);
graphic.endFill();
return graphic;
}
// Init
setup();
})()
/* styles */
/* called by your view template */
* {
box-sizing: border-box;
}
body, html{
margin: 0;
padding: 0;
color:white;
}
#ui{
position: absolute;
z-index: 2;
top: 0;
width: 0;
left: 0;
bottom: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.8.1/pixi.min.js"></script>
<script>PIXI.utils.skipHello();</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
<!-- Click two points to make cubes explode -->
<div id="ui">
<button id="dig">
Dig 2k cubes
</button>
<button id="consume">
Consume 50 cubes
</button>
<p>
Total : <span id="total"></span>
</p>
</div>
Thanks
Using Sprites will always be faster than using Graphics (at least until v5 comes along!)
So I changed your cube creation function to
function cube(size) {
const sprite = new PIXI.Sprite(PIXI.Texture.WHITE);
sprite.tint = Math.random() * 0xFFFFFF;
sprite.width = sprite.height = size;
return sprite;
}
And that boosted the fps for myself
I'm trying to make this code work with more than one id and i can't make it work.
I have tried with querySelectorAll, but with not succes.
I also read this article, but none of the options worked for me
Can anyone help me?
This is the code:
<script>
function Scroller(options) {
this.svg = options.el;
//Animation will end when the end is at which point of othe page. .9 is at about 90% down the page/
// .1 is 10% from the top of the page. Default is middle of the page.
this.animationBounds = {};
this.animationBounds.top = options.startPoint || .5;
this.animationBounds.bottom = options.endPoint || .5;
this.animationBounds.containerBounds = this.svg.getBoundingClientRect();
this.start = this.getPagePosition('top');
this.end = this.getPagePosition('bottom');
this.svgLength = this.svg.getTotalLength();
this.svg.style.strokeDasharray = this.svgLength;
this.animateLine();
window.addEventListener('scroll', this.animateLine.bind(this));
}
Scroller.prototype.getPagePosition = function (position) {
//These positions are all relative to the current window. So they top of the page will be negative and thus need to be
//subtracted to get a positive number
var distanceFromPageTop = document.body.getBoundingClientRect().top;
var divPosition = this.animationBounds.containerBounds[position];
var startPointInCurrentWindow = window.innerHeight * this.animationBounds[position];
return divPosition - distanceFromPageTop - startPointInCurrentWindow;
};
Scroller.prototype.animateLine = function () {
this.currentVisiblePosition = window.pageYOffset;
if (this.currentVisiblePosition < this.start) {
this.svg.style.strokeDashoffset = this.svgLength;
}
if (this.currentVisiblePosition > this.end) {
this.svg.style.strokeDashoffset = '0px';
}
if (this.currentVisiblePosition > this.start && this.currentVisiblePosition < this.end) {
this.svg.style.strokeDashoffset = this.distanceRemaining() * this.pixelsPerVerticalScroll() + 'px';
}
};
Scroller.prototype.distanceRemaining = function () {
return this.end - this.currentVisiblePosition;
};
Scroller.prototype.pixelsPerVerticalScroll = function () {
this.verticalDistance = this.end - this.start;
return this.svgLength / this.verticalDistance;
};
new Scroller({
'el': **document.getElementById('line')**,
'startPoint': .8,
'endPoint': .5
});
</script>
Loop over all the elements matching the selector.
var lines = document.querySelectorAll("#line, #line1, #line2");
for (var i = 0; i < lines.length; i++) {
new Scroller({
el: lines[i],
startPoint: .8,
endPoint: .5
});
}
There is such a thing.
https://jsfiddle.net/j6u6wp7x/1/
var scene;
var controller;
$(document).ready(function() {
parallaxAuto();
$('.viewer__nav div').click(function(event) {
var num = $(this).attr('data-num');
if (num == 'sticky') {
controller.scrollTo(scene);
}
var scrollPos = controller.info("scrollPos");
});
});
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);
}
}
Below there are 3 smaller images that create Navigation. I made it to jump to the top, but I cannot figure out how to jump to 2nd or 3rd.
var scrollPos = controller.info ( "scrollPos"); shows the current position, but I cannot imagine how to use it correctly.
I didn't go through the tit-bits of your code, but in similar situations we used the scrollIntoView() function of JavaScript(not jQuery).
var element = document.getElementById('id of your image');
element.scrollIntoView(false);
This much code should do the trick.
Hope it helps.
EDIT :1
Hi, I updated your fiddle, I think we need to store the scenes in a array and then refer it later. I guess you were looking for something like that.
I'm using Three.js on an app, currently I'm having issues with the object picking. Some objects are not getting intersected by the rays, but only on certain camera rotations. I'm trying to debug the code and to draw the ray.
I'm using this methods in my code(canvas is a namespace for the Three objects):
C.getXY = function(e) {
var click = {};
click.x = ( e.clientX / window.innerWidth ) * 2 - 1;
click.y = - ( e.clientY / window.innerHeight ) * 2 + 1;
return click;
};
C.doPicking = function(e) {
var picked = false;
if (canvas.boundingBox !== null) {
canvas.scene.remove(canvas.boundingBox);
}
var projector = new THREE.Projector(), click = C.getXY(e);
var ray = projector.pickingRay(new THREE.Vector3(click.x, click.y, 0), canvas.camera);
ray.linePrecision = 0.00000000000000001;
ray.precision = 0.00000000000000001;
var intersects = ray.intersectObjects(canvas.scene.children);
if (intersects.length > 0) {
var i = 0;
var ids = [];
while (i < intersects.length) {
if (intersects[i].object.visible) {
//Object is picked
}
++i;
}
}
};
My question is...are other points to consider in the debuging process?
Ooops...forgot to add mesh.geometry.computeFaceNormals(); before adding the meshes to the scene. Now the picking is working normally.