mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-02 12:08:50 +00:00
This reverts commit 3b53bfd2f7.
This commit is contained in:
@@ -3,412 +3,34 @@ title: "Domain Warp Dissolve"
|
||||
description: "Shader transition with fractal noise domain warping"
|
||||
---
|
||||
|
||||
import { InstallCommand } from "/snippets/install-command.jsx";
|
||||
|
||||
<iframe src="/public/catalog/preview/blocks/domain-warp-dissolve.html" className="w-full aspect-video rounded-xl border border-zinc-200 dark:border-zinc-800" loading="lazy" title="Domain Warp Dissolve preview" />
|
||||
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/domain-warp-dissolve.mp4" poster="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/domain-warp-dissolve.png" autoPlay muted loop playsInline />
|
||||
|
||||
## Install
|
||||
|
||||
<InstallCommand command="npx hyperframes add domain-warp-dissolve" />
|
||||
```bash Terminal
|
||||
npx hyperframes add domain-warp-dissolve
|
||||
```
|
||||
|
||||
That writes one file: `compositions/domain-warp-dissolve.html`.
|
||||
|
||||
## Source
|
||||
## Add it to your video
|
||||
|
||||
<Accordion title={`domain-warp-dissolve.html`}>
|
||||
It runs for 4 seconds at 1920×1080. Paste this into your composition:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<style>
|
||||
@import url("https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Bebas+Neue&family=JetBrains+Mono:wght@400;700&family=Lato:wght@400;700&display=block");
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="root"
|
||||
data-composition-id="main"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<style>
|
||||
[data-composition-id="main"] {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: #0d1b2a;
|
||||
}
|
||||
|
||||
/* Blueprint grid */
|
||||
.bp-grid {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.025) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
.bp-left {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 1200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.bp-right {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 720px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 60px 60px 60px 40px;
|
||||
}
|
||||
.bp-divider {
|
||||
position: absolute;
|
||||
left: 1200px;
|
||||
top: 60px;
|
||||
bottom: 60px;
|
||||
width: 1px;
|
||||
background: #00f5d4;
|
||||
opacity: 0.15;
|
||||
}
|
||||
.bp-number {
|
||||
font-family: "Space Mono", monospace;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.4em;
|
||||
text-transform: uppercase;
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 60px;
|
||||
color: #00f5d4;
|
||||
}
|
||||
|
||||
/* Right panel typography */
|
||||
.bp-name {
|
||||
font-family: "Bebas Neue", sans-serif;
|
||||
font-size: 38px;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 20px;
|
||||
color: #00f5d4;
|
||||
}
|
||||
.bp-plabel {
|
||||
font-family: "Space Mono", monospace;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.3em;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 10px;
|
||||
color: #00f5d4;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.bp-prompt {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 20px;
|
||||
line-height: 1.6;
|
||||
padding: 16px 20px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 20px;
|
||||
color: #00f5d4;
|
||||
background: rgba(0, 245, 212, 0.06);
|
||||
border: 1px solid rgba(0, 245, 212, 0.12);
|
||||
}
|
||||
.bp-desc {
|
||||
font-family: "Lato", sans-serif;
|
||||
font-size: 17px;
|
||||
line-height: 1.6;
|
||||
color: #8aa8c0;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
/* GL scenes (hidden, just for texture capture) */
|
||||
.scene {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1200px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.scene-label {
|
||||
font-family: "Bebas Neue", sans-serif;
|
||||
font-size: 140px;
|
||||
letter-spacing: 0.06em;
|
||||
opacity: 0.12;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="bp-grid"></div>
|
||||
<div class="bp-divider"></div>
|
||||
<div class="bp-number">01 / 14</div>
|
||||
|
||||
<!-- GL demo scenes (captured as textures, then hidden) -->
|
||||
<div id="s1" class="scene" style="background: #0d1b2a">
|
||||
<div class="scene-label" style="color: #00f5d4">SCENE A</div>
|
||||
</div>
|
||||
<div id="s2" class="scene" style="background: #00f5d4; opacity: 0">
|
||||
<div class="scene-label" style="color: #0d1b2a">SCENE B</div>
|
||||
</div>
|
||||
|
||||
<!-- GL canvas (left panel only) -->
|
||||
<canvas
|
||||
id="gl-canvas"
|
||||
width="1200"
|
||||
height="1080"
|
||||
style="
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1200px;
|
||||
height: 1080px;
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
"
|
||||
>
|
||||
</canvas>
|
||||
|
||||
<!-- Right panel metadata -->
|
||||
<div class="bp-right">
|
||||
<div class="bp-name">Domain Warp Dissolve</div>
|
||||
<div class="bp-plabel">Prompt</div>
|
||||
<div class="bp-prompt">"use domain warp dissolve shader transition"</div>
|
||||
<div class="bp-desc">
|
||||
Cascaded fbm(p + fbm(p)) displaces both scenes along a warp field. Iridescent
|
||||
cosine-palette edge glow.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="driver"
|
||||
class="clip"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-track-index="0"
|
||||
style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
var glCanvas = document.getElementById("gl-canvas");
|
||||
var gl = glCanvas.getContext("webgl", { preserveDrawingBuffer: true });
|
||||
if (!gl) {
|
||||
console.warn("WebGL not available");
|
||||
window.__timelines["main"] = gsap.timeline({ paused: true });
|
||||
} else {
|
||||
gl.viewport(0, 0, 1200, 1080);
|
||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
||||
var sceneTextures = {};
|
||||
|
||||
function captureScene(sceneId) {
|
||||
var scene = document.getElementById(sceneId);
|
||||
var origOpacity = scene.style.opacity;
|
||||
var origZ = scene.style.zIndex;
|
||||
scene.style.opacity = "1";
|
||||
scene.style.zIndex = "999";
|
||||
scene.offsetHeight;
|
||||
var c = document.createElement("canvas");
|
||||
c.width = 1200;
|
||||
c.height = 1080;
|
||||
var ctx = c.getContext("2d");
|
||||
ctx.fillStyle = window.getComputedStyle(scene).backgroundColor;
|
||||
ctx.fillRect(0, 0, 1200, 1080);
|
||||
var sr = scene.getBoundingClientRect();
|
||||
var els = scene.querySelectorAll("*");
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
var el = els[i],
|
||||
cs = window.getComputedStyle(el);
|
||||
if (cs.display === "none" || cs.visibility === "hidden") continue;
|
||||
var r = el.getBoundingClientRect();
|
||||
if (r.width < 1 || r.height < 1) continue;
|
||||
var x = r.left - sr.left,
|
||||
y = r.top - sr.top,
|
||||
w = r.width,
|
||||
h = r.height;
|
||||
ctx.save();
|
||||
ctx.globalAlpha = parseFloat(cs.opacity) || 1;
|
||||
var bg = cs.backgroundColor;
|
||||
if (bg && bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") {
|
||||
ctx.fillStyle = bg;
|
||||
ctx.fillRect(x, y, w, h);
|
||||
}
|
||||
var hasChildEls = el.querySelector("div,span,img,video");
|
||||
var text = "";
|
||||
for (var j = 0; j < el.childNodes.length; j++)
|
||||
if (el.childNodes[j].nodeType === 3) text += el.childNodes[j].textContent;
|
||||
text = text.trim();
|
||||
if (text && !hasChildEls) {
|
||||
ctx.font = cs.fontWeight + " " + cs.fontSize + " " + cs.fontFamily;
|
||||
ctx.fillStyle = cs.color;
|
||||
if (cs.letterSpacing && cs.letterSpacing !== "normal")
|
||||
ctx.letterSpacing = cs.letterSpacing;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(text, x + w / 2, y + h / 2);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
scene.style.opacity = origOpacity;
|
||||
scene.style.zIndex = origZ;
|
||||
var tex = gl.createTexture();
|
||||
gl.bindTexture(gl.TEXTURE_2D, tex);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);
|
||||
sceneTextures[sceneId] = tex;
|
||||
}
|
||||
|
||||
var vertSrc =
|
||||
"attribute vec2 a_pos; varying vec2 v_uv; void main(){" +
|
||||
"v_uv=a_pos*0.5+0.5; v_uv.y=1.0-v_uv.y; gl_Position=vec4(a_pos,0,1);}";
|
||||
var quadBuf = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf);
|
||||
gl.bufferData(
|
||||
gl.ARRAY_BUFFER,
|
||||
new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
|
||||
gl.STATIC_DRAW,
|
||||
);
|
||||
function compileShader(src, type) {
|
||||
var s = gl.createShader(type);
|
||||
gl.shaderSource(s, src);
|
||||
gl.compileShader(s);
|
||||
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS))
|
||||
console.error("Shader:", gl.getShaderInfoLog(s));
|
||||
return s;
|
||||
}
|
||||
function mkProg(fragSrc) {
|
||||
var p = gl.createProgram();
|
||||
gl.attachShader(p, compileShader(vertSrc, gl.VERTEX_SHADER));
|
||||
gl.attachShader(p, compileShader(fragSrc, gl.FRAGMENT_SHADER));
|
||||
gl.linkProgram(p);
|
||||
if (!gl.getProgramParameter(p, gl.LINK_STATUS))
|
||||
console.error("Link:", gl.getProgramInfoLog(p));
|
||||
return p;
|
||||
}
|
||||
|
||||
var H =
|
||||
"precision mediump float;" +
|
||||
"varying vec2 v_uv;" +
|
||||
"uniform sampler2D u_from, u_to;" +
|
||||
"uniform float u_progress;" +
|
||||
"uniform vec2 u_resolution;\n";
|
||||
|
||||
var NQ =
|
||||
"float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}" +
|
||||
"float vnoise(vec2 p){vec2 i=floor(p),f=fract(p);" +
|
||||
"f=f*f*f*(f*(f*6.-15.)+10.);" +
|
||||
"return mix(mix(hash(i),hash(i+vec2(1,0)),f.x)," +
|
||||
"mix(hash(i+vec2(0,1)),hash(i+vec2(1,1)),f.x),f.y);}" +
|
||||
"float fbm(vec2 p){float v=0.,a=.5;" +
|
||||
"mat2 R=mat2(.8,.6,-.6,.8);" +
|
||||
"for(int i=0;i<5;i++){v+=a*vnoise(p);p=R*p*2.02;a*=.5;}return v;}";
|
||||
var CP =
|
||||
"vec3 palette(float t,vec3 a,vec3 b,vec3 c,vec3 d){" + "return a+b*cos(6.2832*(c*t+d));}";
|
||||
|
||||
var progPass = mkProg(H + "void main(){gl_FragColor=texture2D(u_from,v_uv);}");
|
||||
var progTrans = mkProg(
|
||||
H +
|
||||
NQ +
|
||||
CP +
|
||||
"void main(){vec2 q=vec2(fbm(v_uv*3.),fbm(v_uv*3.+vec2(5.2,1.3)));vec2 r=vec2(fbm(v_uv*3.+q*4.+vec2(1.7,9.2)),fbm(v_uv*3.+q*4.+vec2(8.3,2.8)));float n=fbm(v_uv*3.+r*2.);vec2 warpDir=(q-.5)*.4;vec4 A=texture2D(u_from,clamp(v_uv+warpDir*u_progress,0.,1.));vec4 B=texture2D(u_to,clamp(v_uv-warpDir*(1.-u_progress),0.,1.));float e=smoothstep(u_progress-.08,u_progress+.08,n);float ed=abs(n-u_progress);float em=smoothstep(.1,0.,ed)*(1.-step(1.,u_progress));vec3 ec=palette(ed*8.,vec3(.5),vec3(.5),vec3(1.),vec3(0.,.33,.67));gl_FragColor=vec4(mix(B,A,e).rgb+ec*em*2.,1.);}",
|
||||
);
|
||||
|
||||
function renderShader(prog, texFrom, texTo, progress) {
|
||||
gl.useProgram(prog);
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texFrom);
|
||||
gl.uniform1i(gl.getUniformLocation(prog, "u_from"), 0);
|
||||
gl.activeTexture(gl.TEXTURE1);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texTo);
|
||||
gl.uniform1i(gl.getUniformLocation(prog, "u_to"), 1);
|
||||
gl.uniform1f(gl.getUniformLocation(prog, "u_progress"), progress);
|
||||
gl.uniform2f(gl.getUniformLocation(prog, "u_resolution"), 1200, 1080);
|
||||
var pos = gl.getAttribLocation(prog, "a_pos");
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf);
|
||||
gl.enableVertexAttribArray(pos);
|
||||
gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
function easeInOut(p) {
|
||||
return p < 0.5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2;
|
||||
}
|
||||
|
||||
captureScene("s1");
|
||||
captureScene("s2");
|
||||
glCanvas.style.display = "block";
|
||||
renderShader(progPass, sceneTextures["s1"], sceneTextures["s1"], 0);
|
||||
document.querySelectorAll(".scene").forEach(function (s) {
|
||||
s.style.opacity = "0";
|
||||
});
|
||||
|
||||
var tl = gsap.timeline({
|
||||
paused: true,
|
||||
onUpdate: function () {
|
||||
var t = tl.time();
|
||||
if (t >= 1.0 && t < 3.0) {
|
||||
renderShader(
|
||||
progTrans,
|
||||
sceneTextures["s1"],
|
||||
sceneTextures["s2"],
|
||||
easeInOut((t - 1.0) / 2.0),
|
||||
);
|
||||
} else if (t >= 3.0) {
|
||||
renderShader(progPass, sceneTextures["s2"], sceneTextures["s2"], 0);
|
||||
} else {
|
||||
renderShader(progPass, sceneTextures["s1"], sceneTextures["s1"], 0);
|
||||
}
|
||||
},
|
||||
});
|
||||
tl.to({ v: 0 }, { v: 1, duration: 4, ease: "none" }, 0);
|
||||
window.__timelines["main"] = tl;
|
||||
} // end if (gl)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```html index.html
|
||||
<div
|
||||
data-composition-id="domain-warp-dissolve"
|
||||
data-composition-src="compositions/domain-warp-dissolve.html"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-track-index="1"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
></div>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
## Usage
|
||||
|
||||
After installing, add the block to your host composition:
|
||||
|
||||
```html
|
||||
<div data-composition-id="domain-warp-dissolve" data-composition-src="compositions/domain-warp-dissolve.html" data-start="0" data-duration="4" data-track-index="1" data-width="1920" data-height="1080"></div>
|
||||
```
|
||||
Move it in time with `data-start`. Put it on a different timeline row with
|
||||
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
|
||||
|
||||
{/* hf:generated-footer */}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user