async function fragment(fragmentString, options = {}) {
let {
w = width,
h = 400,
background = 'black',
play = false,
logShader = false,
resolution = 1,
u_texture
} = options
if(logShader) return fragmentString
const canvas = DOM.canvas(w * devicePixelRatio * resolution | 0, h * devicePixelRatio * resolution | 0)
if(w === width) {
w += 28
canvas.width = w * devicePixelRatio
}
canvas.style = `
width: ${w}px;
height: ${h}px;
background: ${background};
`
const gl = canvas.value = canvas.getContext('webgl')
const vertexShader = createShader(gl, gl.VERTEX_SHADER, `
precision mediump float;
attribute vec2 a_position;
uniform vec2 u_resolution;
void main() {
gl_Position = vec4((a_position / u_resolution * 2. - 1.) * vec2(1, -1), 0., 1.);
}`)
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentString)
// create a shader program (= vertex shader + fragment shader)
const program = createProgram(gl, vertexShader, fragmentShader)
// create a buffer to hold vertex positions
const vertexBuffer = createVertexBuffer(gl)
// store uniforms and attributes locations
const a_positionLoc = gl.getAttribLocation(program, 'a_position')
const u_timeLoc = gl.getUniformLocation(program, 'u_time')
const u_resolutionLoc = gl.getUniformLocation(program, 'u_resolution')
const u_mouseLoc = gl.getUniformLocation(program, 'u_mouse')
if (u_texture) u_texture = await loadTexture(gl, u_texture)
// update mouse on 'mousemove'
canvas.addEventListener('mousemove', e => {
gl.uniform2f(u_mouseLoc, e.offsetX * devicePixelRatio * resolution | 0, (h - e.offsetY) * devicePixelRatio * resolution | 0)
})
let startTime = Date.now()
let pauseTimestamp = Date.now()
canvas.addEventListener('click', e => {
play = !play
if(!play) pauseTimestamp = Date.now()
else {
startTime += Date.now() - pauseTimestamp
}
})
let isAnimated = (RegExp(`\\bu_time`, 'g')).test(fragmentString) || (RegExp(`\\bu_mouse`, 'g')).test(fragmentString)
let firstFrame = true;
// rendering loop
function* rendering() {
while(isAnimated || firstFrame) {
firstFrame = false;
// set viewport before drawing
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height)
// define what program for drawing(rendering)
gl.useProgram(program)
// update u_time and u_resolution uniforms
const time = (Date.now() - startTime) / 1000
gl.uniform1f(u_timeLoc, time)
gl.uniform2f(u_resolutionLoc, gl.canvas.width, gl.canvas.height)
if (u_texture) gl.bindTexture(gl.TEXTURE_2D, u_texture)
// clear the canvas before we start drawing on it.
gl.clearColor(1.0, 1.0, Math.sin(time) / 2.0 + 0.5, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
// set vertexBuffer to 'a_position' attribute
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer) // bind vertexBuffer to ARRAY_BUFFER
gl.enableVertexAttribArray(a_positionLoc) // enable individual attributes
const numComponents = 2 // pull out 2 values from vertexBuffer per iteration (=per vertex)
const type = gl.FLOAT // the data in the buffer is 32bit floats
const normalize = false // don't normalize
const stride = 0 // how many bytes to get from one set of values to the next
const offset = 0 // how many bytes inside the buffer to start from
gl.vertexAttribPointer(a_positionLoc, numComponents, type, normalize, stride, offset) // Bind the buffer currently bound to gl.ARRAY_BUFFER to a generic vertex attribute
// make a draw call
const primitiveType = gl.TRIANGLE_STRIP // set how the vertices should connect
const count = 4 // specify the number of indices (vertices) to be rendered
gl.drawArrays(primitiveType, offset, count) // Render primitives from array data
yield canvas
while(!play) yield canvas
}
}
function isScrolledIntoView(el) {
var rect = el.getBoundingClientRect();
var elemTop = rect.top;
var elemBottom = rect.bottom;
// Only completely visible elements return true:
var isVisible = (elemTop >= 0) && (elemBottom <= window.innerHeight);
// Partially visible elements return true:
//isVisible = elemTop < window.innerHeight && elemBottom >= 0;
return isVisible;
}
function isInViewport(element) {
const rect = element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
// start rendering loop
return rendering()
}