Skip to content
Open

Main #60

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions README.md

This file was deleted.

1 change: 1 addition & 0 deletions airbnb/airbnb-nexjs
Submodule airbnb-nexjs added at eefbb5
316 changes: 316 additions & 0 deletions boite_birthday/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>
<title>Document</title>
</head>
<body>
<div class="page">
<div class="header">How it's done</div>
<div class="content">
<p>
The HTML content you're reading right now is overlaid with a
full-screen
<b>&lt;canvas&gt;</b> element. There is a fragment shader that defines
opacity and color for each pixel of the <b>&lt;canvas&gt;</b>. Shader
input values are <b>scroll progress (aka animation progress)</b>,
<b>time</b>, and <b>resolution</b>.
</p>
<p>
While <b>time</b> and <b>window size (resolution)</b> are super easy
to gather, for <b>animation progress</b> I use
<a
href="https://gsap.com/docs/v3/Plugins/ScrollTrigger/"
target="_blank"
>GSAP ScrollTrigger</a
>
plugin.
</p>
<p>
Once the inputs are prepared, we pass them as uniforms to the shader.
The WebGL part of this demo is a basic JS boilerplate to render a
fragment shader on the single full-screen plane. No extra libraries
here.
</p>
<p>
The fragment shader is based on
<a href="https://thebookofshaders.com/13/" target="_blank"
>Fractal Brownian Motion (fBm)</a
>
noise.
</p>
<p>
First, we create a semi-transparent mask to define a contour of
burning paper. It is basically a low-scale fBm noise with
<b>scroll progress</b> value used as a threshold. Taking the same fBm
noise with different thresholds we can
<br />
(a) darken parts of the paper so each pixel gets darker before turning
transparent
<br />
(b) define the stripe along the paper edge and use it as a mask for
flames
</p>
<p>
The fire is done as another two fBm based functions - one for shape
and one for color. Both have a much higher scale and both are animated
with
<b>time</b> value instead of <b>scroll progress</b>.
</p>

<p class="last-line">
<a
href="https://www.linkedin.com/in/ksenia-kondrashova/"
target="_blank"
>linkedIn</a
>
| <a href="https://codepen.io/ksenia-k" target="_blank">codepen</a> |
<a href="https://twitter.com/uuuuuulala" target="_top">twitter (X)</a>
</p>
</div>
</div>

<canvas id="fire-overlay"></canvas>
<div class="scroll-msg">
<div>Hello 👋</div>
<div>scroll me</div>
<div class="arrow-animated">&darr;</div>
</div>

<script type="x-shader/x-fragment" id="vertShader">
precision mediump float;

varying vec2 vUv;
attribute vec2 a_position;

void main() {
vUv = a_position;
gl_Position = vec4(a_position, 0.0, 1.0);
}
</script>

<script type="x-shader/x-fragment" id="fragShader">
precision mediump float;

varying vec2 vUv;
uniform vec2 u_resolution;
uniform float u_progress;
uniform float u_time;

float rand(vec2 n) {
return fract(cos(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);
}
float noise(vec2 n) {
const vec2 d = vec2(0., 1.);
vec2 b = floor(n), f = smoothstep(vec2(0.0), vec2(1.0), fract(n));
return mix(mix(rand(b), rand(b + d.yx), f.x), mix(rand(b + d.xy), rand(b + d.yy), f.x), f.y);
}
float fbm(vec2 n) {
float total = 0.0, amplitude = .4;
for (int i = 0; i < 4; i++) {
total += noise(n) * amplitude;
n += n;
amplitude *= 0.6;
}
return total;
}

void main() {
vec2 uv = vUv;
uv.x *= min(1., u_resolution.x / u_resolution.y);
uv.y *= min(1., u_resolution.y / u_resolution.x);

float t = u_progress;
vec3 color = vec3(1., 1., .95);

float main_noise = 1. - fbm(.75 * uv + 10. - vec2(.3, .9 * t));

float paper_darkness = smoothstep(main_noise - .1, main_noise, t);
color -= vec3(.99, .95, .99) * paper_darkness;

vec3 fire_color = fbm(6. * uv - vec2(0., .005 * u_time)) * vec3(6., 1.4, .0);
float show_fire = smoothstep(.4, .9, fbm(10. * uv + 2. - vec2(0., .005 * u_time)));
show_fire += smoothstep(.7, .8, fbm(.5 * uv + 5. - vec2(0., .001 * u_time)));

float fire_border = .02 * show_fire;
float fire_edge = smoothstep(main_noise - fire_border, main_noise - .5 * fire_border, t);
fire_edge *= (1. - smoothstep(main_noise - .5 * fire_border, main_noise, t));
color += fire_color * fire_edge;

float opacity = 1. - smoothstep(main_noise - .0005, main_noise, t);

gl_FragColor = vec4(color, opacity);
}
</script>
</body>
<script>
const canvasEl = document.querySelector("#fire-overlay");
const scrollMsgEl = document.querySelector(".scroll-msg");

const devicePixelRatio = Math.min(window.devicePixelRatio, 2);
// const devicePixelRatio = 1;

const params = {
fireTime: 0.35,
fireTimeAddition: 0,
};

let st, uniforms;
const gl = initShader();

st = gsap
.timeline({
scrollTrigger: {
trigger: ".page",
start: "0% 0%",
end: "100% 100%",
// markers: true,
scrub: true,
onLeaveBack: () => {
// params.fireTimeAddition = Math.min(params.fireTimeAddition, .3);
// gsap.to(params, {
// duration: .75,
// fireTimeAddition: 0
// })
},
},
})
.to(
scrollMsgEl,
{
duration: 0.1,
opacity: 0,
},
0
)
.to(
params,
{
fireTime: 0.63,
},
0
);

window.addEventListener("resize", resizeCanvas);
resizeCanvas();

gsap.set(".page", {
opacity: 1,
});

function initShader() {
const vsSource = document.getElementById("vertShader").innerHTML;
const fsSource = document.getElementById("fragShader").innerHTML;

const gl =
canvasEl.getContext("webgl") ||
canvasEl.getContext("experimental-webgl");

if (!gl) {
alert("WebGL is not supported by your browser.");
}

function createShader(gl, sourceCode, type) {
const shader = gl.createShader(type);
gl.shaderSource(shader, sourceCode);
gl.compileShader(shader);

if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(
"An error occurred compiling the shaders: " +
gl.getShaderInfoLog(shader)
);
gl.deleteShader(shader);
return null;
}

return shader;
}

const vertexShader = createShader(gl, vsSource, gl.VERTEX_SHADER);
const fragmentShader = createShader(gl, fsSource, gl.FRAGMENT_SHADER);

function createShaderProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);

if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(
"Unable to initialize the shader program: " +
gl.getProgramInfoLog(program)
);
return null;
}

return program;
}

const shaderProgram = createShaderProgram(
gl,
vertexShader,
fragmentShader
);
uniforms = getUniforms(shaderProgram);

function getUniforms(program) {
let uniforms = [];
let uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
for (let i = 0; i < uniformCount; i++) {
let uniformName = gl.getActiveUniform(program, i).name;
uniforms[uniformName] = gl.getUniformLocation(program, uniformName);
}
return uniforms;
}

const vertices = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);

const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

gl.useProgram(shaderProgram);

const positionLocation = gl.getAttribLocation(
shaderProgram,
"a_position"
);
gl.enableVertexAttribArray(positionLocation);

gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

return gl;
}

function render() {
const currentTime = performance.now();
gl.uniform1f(uniforms.u_time, currentTime);

// if (st.scrollTrigger.isActive && st.scrollTrigger.direction === 1) {
// params.fireTimeAddition += .001;
// }

gl.uniform1f(
uniforms.u_progress,
params.fireTime + params.fireTimeAddition
);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

requestAnimationFrame(render);
}

function resizeCanvas() {
canvasEl.width = window.innerWidth * devicePixelRatio;
canvasEl.height = window.innerHeight * devicePixelRatio;
gl.viewport(0, 0, canvasEl.width, canvasEl.height);
gl.uniform2f(uniforms.u_resolution, canvasEl.width, canvasEl.height);
render();
}
</script>
</html>
Loading