press and hold on mobile; I still haven't added a proper scaling/input system for touch devices yet.
xxxxxxxxxx
/*
All of this code was written by me. Feel free to adapt and use in any
project you want - just make sure to link back to my original sketch!
*/
let target = 76, // absolute scroll position
current = 0, // soft scroll position
textboxes = [
{t: 'Scroll Down', x: 200, y: 500, at: 0.00},
{t: 'Keep scrolling', x: 200, y: 500, at: 1000},
{t: 'Just', x: 300, y: 350, at: 2000},
{t: 'Keep', x: 100, y: 320, at: 2300},
{t: 'Scrolling', x: 300, y: 290, at: 2600},
{t: 'woah', x: 100, y: 200, at: 3300},
{t: 'stars!', x: 300, y: 400, at: 3500},
{t: 'ready', x: 100, y: 400, at: 4350},
{t: 'set', x: 300, y: 430, at: 4450},
{t: 'go!!', x: 200, y: 580, at: 4600},
{t: 'Thanks', x: 130, y: 250, at: 6650},
{t: 'for', x: 200, y: 300, at: 6650},
{t: 'watching', x: 270, y: 350, at: 6650}
],
particles = [],
star = [], // static shift amounts
cloud = []; // and sizes
let on_mobile = false;
function setup(){
createCanvas(400, 600);
textAlign(CENTER, CENTER);
// infill static shifts
for(let i=0; i<20; ++i){
star.push({
x: (width+300)*noise(i*20-25,52)-150,
y: height*5*Math.random()
});
}
for(let i=0; i<10; ++i){
cloud.push({
w: noise(i*20)*200-50,
h: noise(i*20)*120-20,
s: -100-300*noise(i*30, 323),
x: 800*noise(i*30 + 53) - 400
});
}
}
function draw(){
if(width < height || touches.length > 1) on_mobile = true; // really sketchy lmao
if(on_mobile && mouseIsPressed) target = Math.max(current-200, Math.min(current+200, target+4));
let fade = (1-Math.min(1, Math.max(0, (current-2700)/400))), // fade from sky -> space
out = Math.max(0, (current - 4530)/1200); // hyperspace jump fadeout
background(160*(fade+out), 160*(fade+out), 255*(fade+out));
noStroke();
for(let i=0; i<star.length; ++i){
let sum = 0;
if(fade < 0.7){
for(let s=0; s<3; ++s) sum += Math.max(0, 1-Math.abs( // compute amount of twinkle
(current/400 + s*8.333)%(star.length+2)-i-1
)*3);
}
// twinkling only occurs when the rocket is in space
sum *= 1-fade;
sum *= Math.max(0, 1-out*30);
// positions are precomputed and wraparound
let y = (current/2+star[i].y)%(height+20)-10,
twinkle = 0.2 + Math.min(1, sum)*0.8;
// stars are dim and barely apparent in the sky etc
fill(255, twinkle*(255-fade*200*(1+Math.max(0, (y-200)/2000))));
push();
translate(star[i].x, y);
scale(1, 1+out*200);
rotate((1-Math.cos(sum*Math.PI))/2*Math.PI*(1-2*(i%2))); // rotate during twinkling
// approximate "star" shape with two rhombi which grow in opposite directions
beginShape();
vertex(3-2*twinkle, 0);
vertex(0, 24*twinkle-3);
vertex(2*twinkle-3, 0);
vertex(0, 3-24*twinkle);
endShape(CLOSE);
beginShape();
vertex(0, 3-2*twinkle);
vertex(12*twinkle-1, 0);
vertex(0, 2*twinkle-3);
vertex(1-12*twinkle, 0);
endShape(CLOSE);
pop();
}
// remove expired particles
particles = particles.filter(x => x.age < 68);
// ensure target is within bounds
target = Math.min(6650, Math.max(0, target));
// current moves 1/30th of the way towards target, creating
// a soft scroll effect
let dc = Math.max(-10, Math.min(10, (target - current)/30));
current += dc;
// update particles
for(let particle of particles) particle.update(dc);
// spawn particles normalized to update speed
for(let i=0.05; i < dc; ++i)
engine(200, 250, Math.PI*1.5, i-0.05);
// draw clouds
push(); translate(0, current);
let yp = 0, d = current/300;
for(let j=0; j<10; ++j){
push(); translate(cloud[j].x, 0);
// only draw clouds in frame
if(Math.abs(yp + current - 50) < 500)
for(let i=0; i<10; ++i){
noStroke(); fill(255 - 5*(10-i), 100);
ellipse(200+90*noise(i+10 + j*50,d)-45,
200-i+18*noise(i+20 + j*50,d)-9+140-noise(i+30 + j*50,d)*70,
50+noise(i + j*50,d)*90 + cloud[j].w,
30+noise(i+30 + j*50,d)*30 + cloud[j].h);
}
pop(); translate(0, cloud[j].s);
yp += cloud[j].s;
}
pop();
// once hyperspace jump complete add bg layer for text to overlay
if(out > 1.53){
fill(255); rect(0, 0, width, height);
}
// draw textboxes
noStroke(); fill(255 - 255*(fade+out)); textSize(30);
for(let t of textboxes){
// lerp in and lerp out using a quartic polynomial transformation
let y = Math.max(0, (Math.abs(current-t.at)-100))*
Math.pow((current - t.at)/200, 3) + (current-t.at)/3;
// ^ also move at constant velocity between lerps
text(t.t, t.x, t.y + y);
}
// hide rocket & flame after jump complete
if(out < 1.53){
push(); translate(200, 300);
// squeeze/stretch effect during jump
scale(1 - Math.max(0, out-0.5), 1 + Math.max(0, out-0.5)*10);
translate(-200, -300);
translate(noise(current/100)*10-5, noise(current/60+1000)*10-5); // subtle shake in flight
// draw fins which are on back side of the rocket
stroke(70); fill(100);
for(let i=0; i<3; ++i){
let rot = (Math.PI * 2 / 3 * i + current/90 + Math.PI/6) % TWO_PI;
if(rot < Math.PI) continue;
beginShape();
vertex(200 + Math.cos(rot)*10, 250);
vertex(200 + Math.cos(rot)*10, 220);
vertex(200 + Math.cos(rot)*20, 240);
vertex(200 + Math.cos(rot)*20, 260);
endShape(CLOSE);
}
// draw exhaust
for(let particle of particles) particle.draw();
// draw rocket body
rect(190, 160, 20, 90);
beginShape();
vertex(200, 100);
vertex(207, 130);
vertex(210, 160);
vertex(190, 160);
vertex(193, 130);
endShape(CLOSE);
// draw fins which are on front side of the rocket
for(let i=0; i<3; ++i){
let rot = (Math.PI * 2 / 3 * i + current/90 + Math.PI/6) % TWO_PI;
if(rot > Math.PI) continue;
beginShape();
vertex(200 + Math.cos(rot)*10, 250);
vertex(200 + Math.cos(rot)*10, 220);
vertex(200 + Math.cos(rot)*20, 240);
vertex(200 + Math.cos(rot)*20, 260);
endShape(CLOSE);
}
pop();
// initial flash as jump starts
fill(255, Math.max(0, 1-Math.abs(current - 4530)/40)*200);
rect(0, 0, width, height);
// overlay entire scene with white during jump
fill(255, out*150); rect(0, 0, width, height);
}
}
function mouseWheel(e){
// capture scroll event and update raw scroll target
target = Math.max(current-200, Math.min(current+200,
target + Math.max(-10, Math.min(10, e.delta / 5))));
}