About this Code
Hello reader, I am atzedent. Nice to meet you again in this free code sharing website. In this post I am going to explain about The artifact's Guts.
If you are new to this website, we recommend you to subscribe to our youtube channel and watch the videos. Ok lets dive into the code.
Images
These are the output images of the code. You can click on the image to enlarge it. also you can click the try it button to see the output.
Youtube Tutorial Video
If you are a visual learner and want to learn how to use this code, you can watch the video below.
and also this video contains the clear step by step explanation and the output of the code.
VIDEO
Source Code
This is the source code of the code. You can copy and paste the code to your editor.
@charset "UTF-8";
@import url(https://fonts.googleapis.com/css?family=Nunito+Sans:300,400,600,700,800);
*,
:after,
:before {
box-sizing: border-box;
padding: 0;
margin: 0;
}
::-webkit-scrollbar {
width: 0.625rem;
height: 0.625rem;
}
::-webkit-scrollbar-thumb {
background: #111;
border-radius: 0.3125rem;
box-shadow: inset 0.125rem 0.125rem 0.125rem rgba(255, 255, 255, 0.25),
inset -0.125rem -0.125rem 0.125rem rgba(0, 0, 0, 0.25);
cursor: default;
}
::-webkit-scrollbar-track {
background: #333;
}
::selection {
background: #fff;
color: #333;
}
html,
body {
height: 100vh;
height: 100dvh;
margin: 0;
overflow: hidden;
}
body {
display: grid;
grid-template-rows: calc(100dvh - 4rem) 4rem;
font-family: system-ui, sans-serif;
}
canvas,
.editor,
#controls {
grid-row: 1;
grid-column: 1;
}
canvas {
width: 100%;
height: auto;
object-fit: contain;
background: black;
touch-action: none;
}
.editor,
.overlay,
#error {
background: repeating-linear-gradient(0deg, #000a, #1119, #000a 0.25rem);
padding: 1em;
}
.editor {
color: #fefefe;
tab-size: 2;
border: none;
resize: none;
}
.editor:focus {
outline: none;
}
#error {
grid-row: 2;
grid-column: 1;
margin: 0;
padding-block: 0;
padding-top: 0.5em;
color: firebrick;
overflow: auto;
text-wrap: pretty;
}
#indicator {
visibility: hidden;
position: absolute;
top: calc(var(--top, 0px) - var(--scroll-top, 0px));
width: 0;
height: 0;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-left: 10px solid firebrick;
transform: translateY(-25%);
}
.overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
margin: 0;
}
.editor,
.overlay {
font-size: 1rem;
line-height: 1.2;
white-space: pre;
}
#controls {
position: fixed;
top: 1em;
right: 2em;
}
.controls {
position: relative;
display: flex;
gap: 1.5em;
padding: 0.5em 1.25em;
background: #1111;
border-radius: 4px;
}
.controls::before,
.controls::after {
content: "";
position: absolute;
z-index: -1;
inset: 0;
transform: scale(0.95);
border-radius: inherit;
opacity: 0;
}
.controls::before {
background: #aef;
animation: pulse 2s infinite;
}
.controls::after {
background: #fefefe66;
transition: transform 200ms ease-in-out;
}
.controls:hover::before,
.controls:hover::after {
opacity: 1;
}
.controls:hover::before {
transform: scale(0.98);
filter: blur(2px);
}
.controls:hover::after {
transform: scale(1.025, 1.1);
}
.controls:hover {
background: #111f;
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.0125);
}
100% {
transform: scale(1);
}
}
.hidden {
display: none !important;
}
.opaque {
opacity: 1 !important;
background: #111 !important;
}
input {
all: unset;
opacity: 0.2;
filter: saturate(0) invert(1);
cursor: pointer;
transition: opacity 200ms ease-in-out;
padding: 0.25em 0.5em;
}
input:hover {
opacity: 1;
}
.icon {
text-align: center;
line-height: 1;
}
#btnToggleView {
width: 1.25em;
}
#btnToggleView::after {
content: "👁";
}
#btnToggleView:checked::after {
content: "✏️";
}
#btnToggleResolution::after {
content: "1️⃣";
}
#btnToggleResolution:checked::after {
content: "2️⃣";
}
#btnReset::after {
content: "⏮️";
}
console.log("Event Fired")
/*********
* made by Matthias Hurrle (@atzedent)
*/
let editMode = false; // set to false to hide the code editor on load
let resolution = 0.5; // set 1 for full resolution or to .5 to start with half resolution on load
let renderDelay = 1000; // delay in ms before rendering the shader after a change
let dpr = Math.max(1, resolution * window.devicePixelRatio);
let frm, source, editor, store, renderer, pointers;
const shaderId = "It Is All Just a Reflection";
window.onload = init;
function resize() {
const { innerWidth: width, innerHeight: height } = window;
canvas.width = width * dpr;
canvas.height = height * dpr;
if (renderer) {
renderer.updateScale(dpr);
}
}
function toggleView() {
editor.hidden = btnToggleView.checked;
}
function reset() {
let shader = source;
editor.text = shader ? shader.textContent : renderer.defaultSource;
store.putShaderSource(shaderId, editor.text);
renderThis();
}
function toggleResolution() {
resolution = btnToggleResolution.checked ? 0.5 : 1;
dpr = Math.max(1, resolution * window.devicePixelRatio);
pointers.updateScale(dpr);
resize();
}
function loop(now) {
renderer.updateMouse(pointers.first);
renderer.updatePointerCount(pointers.count);
renderer.updatePointerCoords(pointers.coords);
renderer.updateMove(pointers.move);
renderer.render(now);
frm = requestAnimationFrame(loop);
}
function renderThis() {
editor.clearError();
store.putShaderSource(shaderId, editor.text);
const result = renderer.test(editor.text);
if (result) {
editor.setError(result);
} else {
renderer.updateShader(editor.text);
}
cancelAnimationFrame(frm); // Always cancel the previous frame!
loop(0);
}
const debounce = (fn, delay) => {
let timerId;
return (...args) => {
clearTimeout(timerId);
timerId = setTimeout(() => fn.apply(this, args), delay);
};
};
const render = debounce(renderThis, renderDelay);
function init() {
source = document.querySelector("script[type='x-shader/x-fragment']");
document.title = "🥚";
renderer = new Renderer(canvas, dpr);
pointers = new PointerHandler(canvas, dpr);
store = new Store(window.location);
editor = new Editor(codeEditor, error, indicator);
editor.text = source.textContent;
renderer.setup();
renderer.init();
if (!editMode) {
btnToggleView.checked = true;
toggleView();
}
if (resolution === 0.5) {
btnToggleResolution.checked = true;
toggleResolution();
}
canvas.addEventListener("shader-error", (e) => editor.setError(e.detail));
resize();
if (renderer.test(source.textContent) === null) {
renderer.updateShader(source.textContent);
}
loop(0);
window.onresize = resize;
window.addEventListener("keydown", (e) => {
if (e.key === "L" && e.ctrlKey) {
e.preventDefault();
btnToggleView.checked = !btnToggleView.checked;
toggleView();
}
});
}
class Renderer {
#vertexSrc = "#version 300 es\nprecision highp float;\nin vec4 position;\nvoid main(){gl_Position=position;}";
#fragmtSrc = "#version 300 es\nprecision highp float;\nout vec4 O;\nuniform float time;\nuniform vec2 resolution;\nvoid main() {\n\tvec2 uv=gl_FragCoord.xy/resolution;\n\tO=vec4(uv,sin(time)*.5+.5,1);\n}";
#vertices = [-1, 1, -1, -1, 1, 1, 1, -1];
constructor(canvas, scale) {
this.canvas = canvas;
this.scale = scale;
this.gl = canvas.getContext("webgl2");
this.gl.viewport(0, 0, canvas.width * scale, canvas.height * scale);
this.shaderSource = this.#fragmtSrc;
this.mouseMove = [0, 0];
this.mouseCoords = [0, 0];
this.pointerCoords = [0, 0];
this.nbrOfPointers = 0;
}
get defaultSource() {
return this.#fragmtSrc;
}
updateShader(source) {
this.reset();
this.shaderSource = source;
this.setup();
this.init();
}
updateMove(deltas) {
this.mouseMove = deltas;
}
updateMouse(coords) {
this.mouseCoords = coords;
}
updatePointerCoords(coords) {
this.pointerCoords = coords;
}
updatePointerCount(nbr) {
this.nbrOfPointers = nbr;
}
updateScale(scale) {
this.scale = scale;
this.gl.viewport(0, 0, this.canvas.width * scale, this.canvas.height * scale);
}
compile(shader, source) {
const gl = this.gl;
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
this.canvas.dispatchEvent(
new CustomEvent("shader-error", { detail: gl.getShaderInfoLog(shader) })
);
}
}
test(source) {
let result = null;
const gl = this.gl;
const shader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
result = gl.getShaderInfoLog(shader);
}
if (gl.getShaderParameter(shader, gl.DELETE_STATUS)) {
gl.deleteShader(shader);
}
return result;
}
reset() {
const { gl, program, vs, fs } = this;
if (!program || gl.getProgramParameter(program, gl.DELETE_STATUS)) return;
if (gl.getShaderParameter(vs, gl.DELETE_STATUS)) {
gl.detachShader(program, vs);
gl.deleteShader(vs);
}
if (gl.getShaderParameter(fs, gl.DELETE_STATUS)) {
gl.detachShader(program, fs);
gl.deleteShader(fs);
}
gl.deleteProgram(program);
}
setup() {
const gl = this.gl;
this.vs = gl.createShader(gl.VERTEX_SHADER);
this.fs = gl.createShader(gl.FRAGMENT_SHADER);
this.compile(this.vs, this.#vertexSrc);
this.compile(this.fs, this.shaderSource);
this.program = gl.createProgram();
gl.attachShader(this.program, this.vs);
gl.attachShader(this.program, this.fs);
gl.linkProgram(this.program);
if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(this.program));
}
}
init() {
const { gl, program } = this;
this.buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array(this.#vertices),
gl.STATIC_DRAW
);
const position = gl.getAttribLocation(program, "position");
gl.enableVertexAttribArray(position);
gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);
program.resolution = gl.getUniformLocation(program, "resolution");
program.time = gl.getUniformLocation(program, "time");
program.move = gl.getUniformLocation(program, "move");
program.touch = gl.getUniformLocation(program, "touch");
program.pointerCount = gl.getUniformLocation(program, "pointerCount");
program.pointers = gl.getUniformLocation(program, "pointers");
}
render(now = 0) {
const {
gl,
program,
buffer,
canvas,
mouseMove,
mouseCoords,
pointerCoords,
nbrOfPointers
} = this;
if (!program || gl.getProgramParameter(program, gl.DELETE_STATUS)) return;
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(program);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.uniform2f(program.resolution, canvas.width, canvas.height);
gl.uniform1f(program.time, now * 1e-3);
gl.uniform2f(program.move, ...mouseMove);
gl.uniform2f(program.touch, ...mouseCoords);
gl.uniform1i(program.pointerCount, nbrOfPointers);
gl.uniform2fv(program.pointers, pointerCoords);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
}
class Store {
constructor(key) {
this.key = key;
this.store = window.localStorage;
}
#ownShadersKey = "ownShaders";
#StorageType = Object.freeze({
shader: "fragmentSource",
config: "config"
});
#getKeyPrefix(type) {
return `${type}${btoa(this.key)}`;
}
#getKey(type, name) {
return `${this.#getKeyPrefix(type)}${btoa(name)}`;
}
putShaderSource(name, source) {
const storageType = this.#StorageType.shader;
this.store.setItem(this.#getKey(storageType, name), source);
}
getShaderSource(name) {
const storageType = this.#StorageType.shader;
return this.store.getItem(this.#getKey(storageType, name));
}
deleteShaderSource(name) {
const storageType = this.#StorageType.shader;
this.store.removeItem(this.#getKey(storageType, name));
}
/** @returns {{title:string, uuid:string}[]} */
getOwnShaders() {
const storageType = this.#StorageType.config;
const result = this.store.getItem(
this.#getKey(storageType, this.#ownShadersKey)
);
return result ? JSON.parse(result) : [];
}
/** @param {{title:string, uuid:string}[]} shader */
putOwnShader(shader) {
const ownShaders = this.getOwnShaders();
const storageType = this.#StorageType.config;
const index = ownShaders.findIndex((s) => s.uuid === shader.uuid);
if (index === -1) {
ownShaders.push(shader);
} else {
ownShaders[index] = shader;
}
this.store.setItem(
this.#getKey(storageType, this.#ownShadersKey),
JSON.stringify(ownShaders)
);
}
deleteOwnShader(uuid) {
const ownShaders = this.getOwnShaders();
const storageType = this.#StorageType.config;
this.store.setItem(
this.#getKey(storageType, this.#ownShadersKey),
JSON.stringify(ownShaders.filter((s) => s.uuid !== uuid))
);
this.deleteShaderSource(uuid);
}
/** @param {string[]} keep The names of the shaders to keep*/
cleanup(keep = []) {
const storageType = this.#StorageType.shader;
const ownShaders = this.getOwnShaders().map((s) =>
this.#getKey(storageType, s.uuid)
);
const premadeShaders = keep.map((name) => this.#getKey(storageType, name));
const keysToKeep = [...ownShaders, ...premadeShaders];
const result = [];
for (let i = 0; i < this.store.length; i++) {
const key = this.store.key(i);
if (
key.startsWith(this.#getKeyPrefix(this.#StorageType.shader)) &&
!keysToKeep.includes(key)
) {
result.push(key);
}
}
result.forEach((key) => this.store.removeItem(key));
}
}
class PointerHandler {
constructor(element, scale) {
this.scale = scale;
this.active = false;
this.pointers = new Map();
this.lastCoords = [0, 0];
this.moves = [0, 0];
const map = (element, scale, x, y) => [x * scale, element.height - y * scale];
element.addEventListener("pointerdown", (e) => {
this.active = true;
this.pointers.set(
e.pointerId,
map(element, this.getScale(), e.clientX, e.clientY)
);
});
element.addEventListener("pointerup", (e) => {
if (this.count === 1) {
this.lastCoords = this.first;
}
this.pointers.delete(e.pointerId);
this.active = this.pointers.size > 0;
});
element.addEventListener("pointerleave", (e) => {
if (this.count === 1) {
this.lastCoords = this.first;
}
this.pointers.delete(e.pointerId);
this.active = this.pointers.size > 0;
});
element.addEventListener("pointermove", (e) => {
if (!this.active) return;
this.lastCoords = [e.clientX, e.clientY];
this.pointers.set(
e.pointerId,
map(element, this.getScale(), e.clientX, e.clientY)
);
this.moves = [this.moves[0] + e.movementX, this.moves[1] + e.movementY];
});
}
getScale() {
return this.scale;
}
updateScale(scale) {
this.scale = scale;
}
reset() {
this.pointers.clear();
this.active = false;
this.moves = [0, 0];
}
get count() {
return this.pointers.size;
}
get move() {
return this.moves;
}
get coords() {
return this.pointers.size > 0
? Array.from(this.pointers.values())
.map((p) => [...p])
.flat()
: [0, 0];
}
get first() {
return this.pointers.values().next().value || this.lastCoords;
}
}
class Editor {
constructor(textarea, errorfield, errorindicator) {
this.textarea = textarea;
this.errorfield = errorfield;
this.errorindicator = errorindicator;
textarea.addEventListener("keydown", this.handleKeydown.bind(this));
textarea.addEventListener("scroll", this.handleScroll.bind(this));
}
get hidden() {
return this.textarea.classList.contains("hidden");
}
set hidden(value) {
value ? this.#hide() : this.#show();
}
get text() {
return this.textarea.value;
}
set text(value) {
this.textarea.value = value;
}
get scrollTop() {
return this.textarea.scrollTop;
}
set scrollTop(value) {
this.textarea.scrollTop = value;
}
setError(message) {
this.errorfield.innerHTML = message;
this.errorfield.classList.add("opaque");
const match = message.match(/ERROR: \d+:(\d+):/);
const lineNumber = match ? parseInt(match[1]) : 0;
const overlay = document.createElement("pre");
overlay.classList.add("overlay");
overlay.textContent = "\n".repeat(lineNumber);
document.body.appendChild(overlay);
const offsetTop = parseInt(getComputedStyle(overlay).height);
this.errorindicator.style.setProperty("--top", offsetTop + "px");
this.errorindicator.style.visibility = "visible";
document.body.removeChild(overlay);
}
clearError() {
this.errorfield.textContent = "";
this.errorfield.classList.remove("opaque");
this.errorfield.blur();
this.errorindicator.style.visibility = "hidden";
}
focus() {
this.textarea.focus();
}
#hide() {
for (const el of [this.errorindicator, this.errorfield, this.textarea]) {
el.classList.add("hidden");
}
}
#show() {
for (const el of [this.errorindicator, this.errorfield, this.textarea]) {
el.classList.remove("hidden");
}
this.focus();
}
handleScroll() {
this.errorindicator.style.setProperty(
"--scroll-top",
`${this.textarea.scrollTop}px`
);
}
handleKeydown(event) {
if (event.key === "Tab") {
event.preventDefault();
this.handleTabKey(event.shiftKey);
} else if (event.key === "Enter") {
event.preventDefault();
this.handleEnterKey();
}
}
handleTabKey(shiftPressed) {
if (this.#getSelectedText() !== "") {
if (shiftPressed) {
this.#unindentSelectedText();
return;
}
this.#indentSelectedText();
} else {
this.#indentAtCursor();
}
}
#getSelectedText() {
const editor = this.textarea;
const start = editor.selectionStart;
const end = editor.selectionEnd;
return editor.value.substring(start, end);
}
#indentAtCursor() {
const editor = this.textarea;
const cursorPos = editor.selectionStart;
document.execCommand("insertText", false, "\t");
editor.selectionStart = editor.selectionEnd = cursorPos + 1;
}
#indentSelectedText() {
const editor = this.textarea;
const cursorPos = editor.selectionStart;
const selectedText = this.#getSelectedText();
const lines = selectedText.split("\n");
const indentedText = lines.map((line) => "\t" + line).join("\n");
document.execCommand("insertText", false, indentedText);
editor.selectionStart = cursorPos;
}
#unindentSelectedText() {
const editor = this.textarea;
const cursorPos = editor.selectionStart;
const selectedText = this.#getSelectedText();
const lines = selectedText.split("\n");
const indentedText = lines
.map((line) => line.replace(/^\t/, "").replace(/^ /, ""))
.join("\n");
document.execCommand("insertText", false, indentedText);
editor.selectionStart = cursorPos;
}
handleEnterKey() {
const editor = this.textarea;
const visibleTop = editor.scrollTop;
const cursorPosition = editor.selectionStart;
let start = cursorPosition - 1;
while (start >= 0 && editor.value[start] !== "\n") {
start--;
}
let newLine = "";
while (
start < cursorPosition - 1 &&
(editor.value[start + 1] === " " || editor.value[start + 1] === "\t")
) {
newLine += editor.value[start + 1];
start++;
}
document.execCommand("insertText", false, "\n" + newLine);
editor.selectionStart = editor.selectionEnd =
cursorPosition + 1 + newLine.length;
editor.scrollTop = visibleTop; // Prevent the editor from scrolling
const lineHeight = editor.scrollHeight / editor.value.split("\n").length;
const line = editor.value.substring(0, cursorPosition).split("\n").length;
// Do the actual layout calculation in order to get the correct scroll position
const visibleBottom = editor.scrollTop + editor.clientHeight;
const lineTop = lineHeight * (line - 1);
const lineBottom = lineHeight * (line + 2);
// If the cursor is outside the visible range, scroll the editor
if (lineTop < visibleTop) editor.scrollTop = lineTop;
if (lineBottom > visibleBottom)
editor.scrollTop = lineBottom - editor.clientHeight;
}
}
And also you can click the try it button to see the output of the code in our web Code Playground.
You can also download the code to the zip format by clicking the download button. The download process is little bit complex, you need ti await for 10 seconds. after that you can download the code by clicking the generated download link.
Leave a Comment
You need to login first to comment.