Skip to content

Instantly share code, notes, and snippets.

@thomaswilburn
Last active February 26, 2021 16:15
Show Gist options
  • Save thomaswilburn/a893ad12b8194b20c3ca2b37a6331fe6 to your computer and use it in GitHub Desktop.
Save thomaswilburn/a893ad12b8194b20c3ca2b37a6331fe6 to your computer and use it in GitHub Desktop.
Shader box custom element
<style>
:host {
width: 300px;
height: 150px;
display: block;
position: relative;
}
canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
<canvas as="canvas"></canvas>
class CustomElement extends HTMLElement {
constructor() {
super();
var def = new.target;
this.elements = {};
this.attachShadow({ mode: "open" });
if (def.template) {
this.shadowRoot.innerHTML = def.template;
this.shadowRoot.querySelectorAll(`[as]`).forEach(el => {
var name = el.getAttribute("as");
this.elements[name] = el;
});
}
if (def.boundMethods) {
for (var f of def.boundMethods) {
this[f] = this[f].bind(this);
}
}
if (def.mirroredProps) {
def.mirroredProps.forEach(f => {
Object.defineProperty(this, f, {
get() {
return this.getAttribute(f);
},
set(v) {
this.setAttribute(f, v);
}
})
});
}
}
broadcast(event, detail = {}) {
var e = new CustomEvent(event, { bubbles: true, composed: true, detail });
this.dispatchEvent(e);
}
static define(tag) {
window.customElements.define(tag, this);
}
}
module.exports = CustomElement;
var CustomElement = require("../customElement");
const POLYS = [
-1, 1,
1, 1,
1, -1,
-1, 1,
1, -1,
-1, -1
];
class ShaderBox extends CustomElement {
constructor() {
super();
var gl = this.gl = this.elements.canvas.getContext("webgl");
this.observer = new IntersectionObserver(this.onIntersection);
this.observer.observe(this);
this.visible = false;
this.raf = null;
this.program = null;
var vertex = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertex, `
attribute vec2 coord;
void main() {
gl_Position = vec4(coord, 0.0, 1.0);
}
`);
gl.compileShader(vertex);
gl.vertex = vertex;
this.requesting = null;
this.buffer = gl.createBuffer();
}
static get boundMethods() {
return [
"onIntersection",
"tick"
];
}
static get observedAttributes() {
return [
"src"
]
}
static get mirroredProps() {
return [
"src"
]
}
attributeChangedCallback(attr, was, value) {
switch (attr) {
case "src":
if (was == value) return;
if (this.requesting) {
this.requesting.abort();
}
var options = {};
if ("AbortController" in window) {
this.requesting = new AbortController();
options.signal = this.requesting.signal;
}
fetch(value, options).then(async response => {
this.requesting = null;
var source = await response.text();
this.setShader(source);
}).catch(err => {});
break;
}
}
setShader(shader) {
var gl = this.gl;
var fragment = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragment, shader);
gl.compileShader(fragment);
var error = gl.getShaderInfoLog(fragment);
if (error) {
console.log(error);
return;
}
var program = gl.createProgram();
gl.attachShader(program, fragment);
gl.attachShader(program, gl.vertex);
gl.linkProgram(program);
gl.useProgram(program);
// attributes and uniforms
gl.program = program;
gl.attributes = {
coord: 0
};
for (var a in gl.attributes) gl.attributes[a] = gl.getAttribLocation(program, a);
gl.uniforms = {
u_time: 0,
u_resolution: 0
};
for (var u in gl.uniforms) gl.uniforms[u] = gl.getUniformLocation(program, u);
if (this.raf) cancelAnimationFrame(this.raf);
this.tick();
}
onIntersection([e]) {
this.visible = e.isIntersecting;
if (this.visible) this.tick();
}
tick(t) {
if (!this.visible) return;
if (this.gl.program) {
this.render(t);
}
this.raf = requestAnimationFrame(this.tick);
}
render(t) {
var { buffer, gl } = this;
// require setShader() to be called
if (!gl.program) return;
var canvas = gl.canvas;
gl.enableVertexAttribArray(gl.uniforms.coords);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(POLYS), gl.STATIC_DRAW);
gl.vertexAttribPointer(gl.uniforms.coords, 2, gl.FLOAT, false, 0, 0);
gl.uniform1f(gl.uniforms.u_time, t + 12581372.5324);
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
gl.viewport(0, 0, canvas.width, canvas.height);
gl.uniform2f(gl.uniforms.u_resolution, canvas.width, canvas.height);
gl.clearColor(0, 1, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, POLYS.length / 2);
}
static get template() {
return require("./_shader-box.html");
}
}
ShaderBox.define("shader-box");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment