├── .gitignore ├── LICENSE ├── README.md ├── example ├── .nojekyll └── index.html ├── lib ├── Shadify.js └── Shadify.js.map ├── package-lock.json ├── package.json ├── src ├── Shadify.ts └── index.ts ├── tsconfig.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 KÆD3 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shadify 2 | ![Repository size](https://img.shields.io/github/repo-size/danielfvm/Shadify?color=39d45f) 3 | [![GitHub last commit](https://img.shields.io/github/last-commit/danielfvm/Shadify?color=39d45f)](https://github.com/danielfvm/Shadify/commits/master) 4 | ![License](https://img.shields.io/badge/license-MIT-39d45f) 5 | [![Stargazers](https://img.shields.io/github/stars/danielfvm/Shadify?color=39d45f&logo=github)](https://github.com/danielfvm/Shadify/stargazers) 6 | 7 | A library for adding a shader wallpaper to the background of your website or any of your divs. 8 | You can find a live example [here](https://danielfvm.github.io/Shadify/example/) and you can find the code in the `example/` folder. 9 | 10 | ![untitled](https://github.com/danielfvm/Shadify/assets/23420640/bee4a957-7d08-4db6-849c-7e7ff3bdd1d8) 11 | 12 | 13 | ## Usage 14 | Include shadify in your project. 15 | ```html 16 | 17 | ``` 18 | 19 | To add a shader to the background or any other div add `data-shader` with a link to a [glslsandbox.com](https://glslsandbox.com/) shader or to your own shader source file. 20 | ```html 21 | 22 | ... 23 | 24 | ``` 25 | 26 | Additionally you can set `data-shader-speed` (1.0 default) and `data-shader-quality` (2.0 default) settings. Here an example with the same shader 27 | but at twice the speed and a quarter the quality. 28 | ```html 29 |
30 | .... 31 |
32 | ``` 33 | 34 | ## Features 35 | * Shaders can directly be loaded from [glslsandbox.com](https://glslsandbox.com/) 36 | * Support for setting shader quality and speed 37 | * Supports mouse input 38 | * Attributes can be changed using JavaScript at runtime 39 | * Access the shader uniforms using `myDiv.shadify.getUniform(name)` 40 | 41 | ## Planned 42 | * Support links from `shadertoy.com` 43 | * Improved README.md 44 | * Documentation 45 | * Reuse same shaders 46 | 47 | ## Build library 48 | Run following commands to build this library yourself. You will find the output at `dist/`. 49 | ```bash 50 | git clone https://github.com/danielfvm/Shadify.git 51 | cd Shadify 52 | npm install 53 | npm run build 54 | ``` 55 | -------------------------------------------------------------------------------- /example/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielfvm/Shadify/3ca18b94491e11baa1f02249771b3d418b39a6fe/example/.nojekyll -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Shadify 7 | 8 | 9 | 10 | 11 | 12 |

Welcome to Shadify!

13 |
14 |

Hello World!

15 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

16 |
17 | 18 | 19 | 20 | 21 | 22 | 46 | 47 | 59 | 60 | -------------------------------------------------------------------------------- /lib/Shadify.js: -------------------------------------------------------------------------------- 1 | var Shadify;(()=>{"use strict";var t={336:function(t,e){var i,s=this&&this.__awaiter||function(t,e,i,s){return new(i||(i=Promise))((function(a,r){function n(t){try{o(s.next(t))}catch(t){r(t)}}function h(t){try{o(s.throw(t))}catch(t){r(t)}}function o(t){var e;t.done?a(t.value):(e=t.value,e instanceof i?e:new i((function(t){t(e)}))).then(n,h)}o((s=s.apply(t,e||[])).next())}))};Object.defineProperty(e,"__esModule",{value:!0}),e.Shadify=void 0,function(t){class e{constructor(t,e){if(this.running=!0,this.eventHandlerResize=this.resize.bind(this),this.eventHandlerMouse=this.mouse.bind(this),this._quality=2,this.speed=1,this.target=e,"CANVAS"===this.target.tagName)this.canvas=this.target;else{if(this.canvas=document.createElement("canvas"),e.append(this.canvas),e!==document.body){const t=getComputedStyle(e).borderRadius||0;e.style.clipPath=`inset(0 0 0 0 round ${t})`}this.canvas.style.position="fixed",this.canvas.style.left="0%",this.canvas.style.right="0%",this.canvas.style.top="0%",this.canvas.style.bottom="0%",this.canvas.style.width="100%",this.canvas.style.height="100%"}this.updateAttributes(),this.gl=this.canvas.getContext("webgl")||this.canvas.getContext("experimental-webgl"),this.loadShader(t);let i=new Float32Array([-1,3,-1,-1,3,-1]);this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.gl.createBuffer()),this.gl.bufferData(this.gl.ARRAY_BUFFER,i,this.gl.STATIC_DRAW);let s=this.gl.getAttribLocation(this.pid,"coords");this.gl.vertexAttribPointer(s,2,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(s),window.addEventListener("resize",this.eventHandlerResize),window.addEventListener("mousemove",this.eventHandlerMouse),new MutationObserver((t=>{this.updateAttributes()})).observe(e,{subtree:!1,childList:!1,attributes:!0,attributeFilter:["data-shader","data-shader-quality","data-shader-speed"]}),this.update()}loadShader(t){this.pid&&this.gl.deleteProgram(this.pid),this.vert&&this.gl.deleteShader(this.vert),this.frag&&this.gl.deleteShader(this.frag),this.pid=this.gl.createProgram(),this.vert=i(this.gl,"\n attribute vec2 coords;\n void main(void) {\n gl_Position = vec4(coords.xy, 0.0, 1.0);\n }\n ",this.gl.VERTEX_SHADER),this.gl.attachShader(this.pid,this.vert),this.frag=i(this.gl,t,this.gl.FRAGMENT_SHADER),this.gl.attachShader(this.pid,this.frag),this.gl.linkProgram(this.pid),this.gl.useProgram(this.pid),this.uniformResolution=this.gl.getUniformLocation(this.pid,"resolution"),this.uniformTime=this.gl.getUniformLocation(this.pid,"time"),this.uniformMouse=this.gl.getUniformLocation(this.pid,"mouse")}updateAttributes(){var t,e,i,s;this.quality=Number(null===(t=this.target.attributes.getNamedItem("data-shader-quality"))||void 0===t?void 0:t.value)||this.quality,this.speed=Number(null===(e=this.target.attributes.getNamedItem("data-shader-speed"))||void 0===e?void 0:e.value)||this.speed,this.canvas.style.zIndex=(null===(i=this.target.attributes.getNamedItem("data-shader-z-index"))||void 0===i?void 0:i.value)||"-1",(null===(s=this.target.attributes.getNamedItem("data-shader"))||void 0===s?void 0:s.value)||this.destroy()}resize(){"CANVAS"!==this.target.tagName&&(this.canvas.width=window.innerWidth/this.quality,this.canvas.height=window.innerHeight/this.quality)}mouse(t){this.mouseX=t.clientX,this.mouseY=t.clientY}update(){const{width:t,height:e}=this.canvas;this.running&&(this.gl.uniform2f(this.uniformMouse,this.mouseX/t/this.quality,1-this.mouseY/e/this.quality),this.gl.uniform2f(this.uniformResolution,t,e),this.gl.uniform1f(this.uniformTime,performance.now()/1e3*this.speed),this.gl.viewport(0,0,t,e),this.gl.clearColor(0,0,0,0),this.gl.clear(this.gl.COLOR_BUFFER_BIT),this.gl.drawArrays(this.gl.TRIANGLES,0,3),requestAnimationFrame(this.update.bind(this)))}getUniform(t){return this.gl.getUniformLocation(this.pid,t)}destroy(){this.target.shadify=void 0,this.running=!1,this.gl.deleteProgram(this.pid),this.gl.deleteShader(this.vert),this.gl.deleteShader(this.frag),this.canvas.remove(),this.pid=null,this.vert=null,this.frag=null,window.removeEventListener("resize",this.eventHandlerResize),window.removeEventListener("mousemove",this.eventHandlerMouse)}get quality(){return this._quality}set quality(t){this._quality=t,this.resize()}}function i(t,e,i){const s=t.createShader(i);if(t.shaderSource(s,e),t.compileShader(s),!t.getShaderParameter(s,t.COMPILE_STATUS))throw`Could not compile WebGL program. \n\n${t.getShaderInfoLog(s)}`;return s}function a(t){return s(this,void 0,void 0,(function*(){if(t.includes("glslsandbox.com")&&t.includes("/e#")){t=t.replace("/e#","/item/");const e=yield fetch(t),i=yield e.json();if(void 0===i.code)throw new Error("Could not get shader code");return i.code}const e=yield fetch(t);return yield e.text()}))}function r(t){var i;return s(this,void 0,void 0,(function*(){if(null==t)return;const s=null===(i=t.attributes.getNamedItem("data-shader"))||void 0===i?void 0:i.value;null!=s&&0!=s.trim().length&&(null!=(null==t?void 0:t.shadify)?t.shadify.target==t?t.shadify.loadShader(yield a(s)):t.shadify.destroy():(console.log(t),t.shadify=new e(yield a(s),t)))}))}document.addEventListener("DOMContentLoaded",(function t(){new MutationObserver((t=>{console.log(t,t.filter((e=>t.filter((t=>t.target==e.target)).length>=t.length-1))),t.filter((e=>t.filter((t=>t.target==e.target)).length>=t.length-1)).forEach((t=>r(t.target)))})).observe(document.body,{subtree:!0,childList:!1,attributeFilter:["data-shader"]}),document.querySelectorAll("[data-shader]").forEach((t=>r(t))),document.removeEventListener("DOMContentLoaded",t)}))}(i||(e.Shadify=i={}))},607:(t,e,i)=>{Object.defineProperty(e,"__esModule",{value:!0});const s=i(336);e.default=s.Shadify,Object.assign(t.exports,s.Shadify)}},e={},i=function i(s){var a=e[s];if(void 0!==a)return a.exports;var r=e[s]={exports:{}};return t[s].call(r.exports,r,r.exports,i),r.exports}(607);Shadify=i})(); 2 | //# sourceMappingURL=Shadify.js.map -------------------------------------------------------------------------------- /lib/Shadify.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Shadify.js","mappings":"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","sources":["webpack://Shadify/./src/Shadify.ts","webpack://Shadify/./src/index.ts","webpack://Shadify/webpack/bootstrap","webpack://Shadify/webpack/startup"],"sourcesContent":["\"use strict\";\nvar __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.Shadify = void 0;\nvar Shadify;\n(function (Shadify) {\n const vertShader = `\n attribute vec2 coords;\n void main(void) {\n gl_Position = vec4(coords.xy, 0.0, 1.0);\n }\n `;\n class Wallpaper {\n constructor(fragShader, target) {\n this.running = true;\n this.eventHandlerResize = this.resize.bind(this);\n this.eventHandlerMouse = this.mouse.bind(this);\n this._quality = 2;\n this.speed = 1;\n this.target = target;\n if (this.target.tagName === \"CANVAS\") {\n this.canvas = this.target;\n }\n else {\n this.canvas = document.createElement(\"canvas\");\n target.append(this.canvas);\n // Setting this in body would break the fullscreen look\n if (target !== document.body) {\n const style = getComputedStyle(target);\n const radius = style.borderRadius || 0;\n target.style.clipPath = `inset(0 0 0 0 round ${radius})`;\n }\n this.canvas.style.position = \"fixed\";\n this.canvas.style.left = \"0%\";\n this.canvas.style.right = \"0%\";\n this.canvas.style.top = \"0%\";\n this.canvas.style.bottom = \"0%\";\n this.canvas.style.width = \"100%\";\n this.canvas.style.height = \"100%\";\n }\n // Fetches the attributes and sets quality and speed if given\n this.updateAttributes();\n // Get gl context from canvas\n this.gl = (this.canvas.getContext(\"webgl\") || this.canvas.getContext(\"experimental-webgl\"));\n this.loadShader(fragShader);\n // Create buffer\n let array = new Float32Array([-1, 3, -1, -1, 3, -1]);\n this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.gl.createBuffer());\n this.gl.bufferData(this.gl.ARRAY_BUFFER, array, this.gl.STATIC_DRAW);\n let al = this.gl.getAttribLocation(this.pid, \"coords\");\n this.gl.vertexAttribPointer(al, 2, this.gl.FLOAT, false, 0, 0);\n this.gl.enableVertexAttribArray(al);\n window.addEventListener(\"resize\", this.eventHandlerResize);\n window.addEventListener(\"mousemove\", this.eventHandlerMouse);\n const observer = new MutationObserver((_) => {\n this.updateAttributes();\n });\n observer.observe(target, {\n subtree: false,\n childList: false,\n attributes: true,\n attributeFilter: [\"data-shader\", \"data-shader-quality\", \"data-shader-speed\"],\n });\n this.update();\n }\n loadShader(fragShader) {\n // Before initializing new shader, remove old ones if present\n if (this.pid)\n this.gl.deleteProgram(this.pid);\n if (this.vert)\n this.gl.deleteShader(this.vert);\n if (this.frag)\n this.gl.deleteShader(this.frag);\n // Create shader program\n this.pid = this.gl.createProgram();\n // Compile vertex shader\n this.vert = createShader(this.gl, vertShader, this.gl.VERTEX_SHADER);\n this.gl.attachShader(this.pid, this.vert);\n // Compile fragment shader\n this.frag = createShader(this.gl, fragShader, this.gl.FRAGMENT_SHADER);\n this.gl.attachShader(this.pid, this.frag);\n this.gl.linkProgram(this.pid);\n this.gl.useProgram(this.pid);\n // Read uniforms\n this.uniformResolution = this.gl.getUniformLocation(this.pid, \"resolution\");\n this.uniformTime = this.gl.getUniformLocation(this.pid, \"time\");\n this.uniformMouse = this.gl.getUniformLocation(this.pid, \"mouse\");\n }\n updateAttributes() {\n var _a, _b, _c, _d;\n this.quality = Number((_a = this.target.attributes.getNamedItem(\"data-shader-quality\")) === null || _a === void 0 ? void 0 : _a.value) || this.quality;\n this.speed = Number((_b = this.target.attributes.getNamedItem(\"data-shader-speed\")) === null || _b === void 0 ? void 0 : _b.value) || this.speed;\n this.canvas.style.zIndex = ((_c = this.target.attributes.getNamedItem(\"data-shader-z-index\")) === null || _c === void 0 ? void 0 : _c.value) || \"-1\";\n if (!((_d = this.target.attributes.getNamedItem(\"data-shader\")) === null || _d === void 0 ? void 0 : _d.value)) {\n this.destroy();\n }\n }\n resize() {\n if (this.target.tagName !== \"CANVAS\") {\n this.canvas.width = window.innerWidth / this.quality;\n this.canvas.height = window.innerHeight / this.quality;\n }\n }\n mouse(e) {\n this.mouseX = e.clientX;\n this.mouseY = e.clientY;\n }\n update() {\n const { width, height } = this.canvas;\n if (!this.running)\n return;\n this.gl.uniform2f(this.uniformMouse, this.mouseX / width / this.quality, 1 - this.mouseY / height / this.quality);\n this.gl.uniform2f(this.uniformResolution, width, height);\n this.gl.uniform1f(this.uniformTime, performance.now() / 1000 * this.speed);\n this.gl.viewport(0, 0, width, height);\n this.gl.clearColor(0, 0, 0, 0);\n this.gl.clear(this.gl.COLOR_BUFFER_BIT);\n this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);\n requestAnimationFrame(this.update.bind(this));\n }\n /**\n * Returns a WebGLUniformLocation for the given name in the fragment shader.\n * @param {string} name\n * @returns {WebGLUniformLocation}\n */\n getUniform(name) {\n return this.gl.getUniformLocation(this.pid, name);\n }\n /**\n * This function has to be called to clean up the WebGL context.\n * It is also called when the element is removed from the DOM.\n * @returns {void}\n */\n destroy() {\n this.target.shadify = undefined;\n this.running = false;\n this.gl.deleteProgram(this.pid);\n this.gl.deleteShader(this.vert);\n this.gl.deleteShader(this.frag);\n this.canvas.remove();\n this.pid = null;\n this.vert = null;\n this.frag = null;\n window.removeEventListener(\"resize\", this.eventHandlerResize);\n window.removeEventListener(\"mousemove\", this.eventHandlerMouse);\n }\n get quality() {\n return this._quality;\n }\n set quality(value) {\n this._quality = value;\n this.resize();\n }\n }\n /**\n * Helper function for creating a WebGLShader in Wallpaper class\n * @param {WebGLRenderingContext} gl\n * @param {string} sourceCode\n * @param {number} type\n * @returns {WebGLShader}\n */\n function createShader(gl, sourceCode, type) {\n const shader = gl.createShader(type);\n gl.shaderSource(shader, sourceCode);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const info = gl.getShaderInfoLog(shader);\n throw `Could not compile WebGL program. \\n\\n${info}`;\n }\n return shader;\n }\n /**\n * Fetches the shader code from the given URL.\n * If URL is from glslsandbox.com, it will fetch the code from the item page.\n * @param {string} url\n * @returns {Promise}\n */\n function getCodeFromURL(url) {\n return __awaiter(this, void 0, void 0, function* () {\n // link to glslsandbox website is a special case\n if (url.includes(\"glslsandbox.com\") && url.includes(\"/e#\")) {\n url = url.replace(\"/e#\", \"/item/\");\n const res = yield fetch(url);\n const text = yield res.json();\n if (typeof text.code === \"undefined\")\n throw new Error(\"Could not get shader code\");\n return text.code;\n }\n // otherwise we expect a link to a source file and return the code\n const res = yield fetch(url);\n return yield res.text();\n });\n }\n /**\n * Helper function for handling elements with the \"data-shader\" attribute.\n * @param {HTMLElement} target\n * @returns {void}\n */\n function handleHTMLElement(target) {\n var _a;\n return __awaiter(this, void 0, void 0, function* () {\n if (target == null)\n return;\n const url = (_a = target.attributes.getNamedItem(\"data-shader\")) === null || _a === void 0 ? void 0 : _a.value;\n if (url == null || url.trim().length == 0)\n return;\n if ((target === null || target === void 0 ? void 0 : target.shadify) != null) {\n if (target.shadify.target == target)\n target.shadify.loadShader(yield getCodeFromURL(url));\n else\n target.shadify.destroy();\n }\n else {\n console.log(target);\n target.shadify = new Wallpaper(yield getCodeFromURL(url), target);\n }\n });\n }\n /**\n * Initializes Shadify.\n * @returns {void}\n */\n function init() {\n const observer = new MutationObserver((mutationList) => {\n console.log(mutationList, mutationList\n .filter(x => mutationList.filter(y => y.target == x.target).length >= mutationList.length - 1));\n mutationList\n .filter(x => mutationList.filter(y => y.target == x.target).length >= mutationList.length - 1) // filter duplicate targets\n .forEach((mutation) => handleHTMLElement(mutation.target));\n });\n observer.observe(document.body, {\n subtree: true,\n childList: false,\n attributeFilter: [\"data-shader\"]\n });\n // Handle elements with the \"data-shader\" attribute\n document.querySelectorAll('[data-shader]').forEach((e) => handleHTMLElement(e));\n document.removeEventListener(\"DOMContentLoaded\", init);\n }\n document.addEventListener(\"DOMContentLoaded\", init);\n})(Shadify || (exports.Shadify = Shadify = {}));\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst Shadify_1 = require(\"./Shadify\");\nexports.default = Shadify_1.Shadify;\nObject.assign(module.exports, Shadify_1.Shadify);\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// startup\n// Load entry module and return exports\n// This entry module is referenced by other modules so it can't be inlined\nvar __webpack_exports__ = __webpack_require__(607);\n"],"names":["Shadify","__awaiter","this","thisArg","_arguments","P","generator","Promise","resolve","reject","fulfilled","value","step","next","e","rejected","result","done","then","apply","Object","defineProperty","exports","Wallpaper","constructor","fragShader","target","running","eventHandlerResize","resize","bind","eventHandlerMouse","mouse","_quality","speed","tagName","canvas","document","createElement","append","body","radius","getComputedStyle","borderRadius","style","clipPath","position","left","right","top","bottom","width","height","updateAttributes","gl","getContext","loadShader","array","Float32Array","bindBuffer","ARRAY_BUFFER","createBuffer","bufferData","STATIC_DRAW","al","getAttribLocation","pid","vertexAttribPointer","FLOAT","enableVertexAttribArray","window","addEventListener","MutationObserver","_","observe","subtree","childList","attributes","attributeFilter","update","deleteProgram","vert","deleteShader","frag","createProgram","createShader","VERTEX_SHADER","attachShader","FRAGMENT_SHADER","linkProgram","useProgram","uniformResolution","getUniformLocation","uniformTime","uniformMouse","_a","_b","_c","_d","quality","Number","getNamedItem","zIndex","destroy","innerWidth","innerHeight","mouseX","clientX","mouseY","clientY","uniform2f","uniform1f","performance","now","viewport","clearColor","clear","COLOR_BUFFER_BIT","drawArrays","TRIANGLES","requestAnimationFrame","getUniform","name","shadify","undefined","remove","removeEventListener","sourceCode","type","shader","shaderSource","compileShader","getShaderParameter","COMPILE_STATUS","getShaderInfoLog","getCodeFromURL","url","includes","replace","res","fetch","text","json","code","Error","handleHTMLElement","trim","length","console","log","init","mutationList","filter","x","y","forEach","mutation","querySelectorAll","Shadify_1","assign","module","__webpack_module_cache__","__webpack_exports__","__webpack_require__","moduleId","cachedModule","__webpack_modules__","call"],"sourceRoot":""} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shadify", 3 | "version": "1.0.1", 4 | "description": "A library for adding shaders to the background of your website.", 5 | "main": "lib/shadify.js", 6 | "scripts": { 7 | "build": "NODE_ENV=production node_modules/.bin/webpack --config webpack.config.js --progress" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/danielfvm/Shadify.git" 12 | }, 13 | "keywords": [ 14 | "shader", 15 | "wallpaper", 16 | "animations" 17 | ], 18 | "author": "DeanCode", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/danielfvm/Shadify/issues" 22 | }, 23 | "homepage": "https://github.com/danielfvm/Shadify#readme", 24 | "devDependencies": { 25 | "ts-jest": "^29.1.1", 26 | "typescript": "^5.2.2", 27 | "webpack-cli": "^5.1.4" 28 | }, 29 | "dependencies": { 30 | "ts-loader": "^9.4.4", 31 | "webpack": "^5.88.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Shadify.ts: -------------------------------------------------------------------------------- 1 | export namespace Shadify { 2 | const vertShader = ` 3 | attribute vec2 coords; 4 | void main(void) { 5 | gl_Position = vec4(coords.xy, 0.0, 1.0); 6 | } 7 | `; 8 | 9 | class Wallpaper { 10 | public gl: WebGLRenderingContext; 11 | public canvas: HTMLCanvasElement; 12 | 13 | private uniformResolution: WebGLUniformLocation; 14 | private uniformTime: WebGLUniformLocation; 15 | private uniformMouse: WebGLUniformLocation; 16 | 17 | private pid: WebGLProgram; 18 | private vert: WebGLShader; 19 | private frag: WebGLShader; 20 | 21 | private target: HTMLElement; 22 | 23 | private mouseX: number; 24 | private mouseY: number; 25 | 26 | private running = true; 27 | 28 | private eventHandlerResize = this.resize.bind(this); 29 | private eventHandlerMouse = this.mouse.bind(this); 30 | 31 | private _quality: number = 2; 32 | public speed: number = 1; 33 | 34 | constructor(fragShader: string, target: HTMLElement) { 35 | this.target = target; 36 | 37 | if (this.target.tagName === "CANVAS") { 38 | this.canvas = this.target as HTMLCanvasElement; 39 | } else { 40 | this.canvas = document.createElement("canvas"); 41 | target.append(this.canvas); 42 | 43 | // Setting this in body would break the fullscreen look 44 | if (target !== document.body) { 45 | const style = getComputedStyle(target); 46 | const radius = style.borderRadius || 0; 47 | target.style.clipPath = `inset(0 0 0 0 round ${radius})`; 48 | } 49 | 50 | this.canvas.style.position = "fixed"; 51 | this.canvas.style.left = "0%"; 52 | this.canvas.style.right = "0%"; 53 | this.canvas.style.top = "0%"; 54 | this.canvas.style.bottom = "0%"; 55 | this.canvas.style.width = "100%"; 56 | this.canvas.style.height = "100%"; 57 | } 58 | 59 | // Fetches the attributes and sets quality and speed if given 60 | this.updateAttributes(); 61 | 62 | // Get gl context from canvas 63 | this.gl = (this.canvas.getContext("webgl") || this.canvas.getContext("experimental-webgl")) as WebGLRenderingContext; 64 | this.loadShader(fragShader); 65 | 66 | // Create buffer 67 | let array = new Float32Array([-1, 3, -1, -1, 3, -1]); 68 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.gl.createBuffer()); 69 | this.gl.bufferData(this.gl.ARRAY_BUFFER, array, this.gl.STATIC_DRAW); 70 | 71 | let al = this.gl.getAttribLocation(this.pid, "coords"); 72 | this.gl.vertexAttribPointer(al, 2, this.gl.FLOAT, false, 0, 0); 73 | this.gl.enableVertexAttribArray(al); 74 | 75 | window.addEventListener("resize", this.eventHandlerResize); 76 | window.addEventListener("mousemove", this.eventHandlerMouse); 77 | 78 | const observer = new MutationObserver((_) => { 79 | this.updateAttributes(); 80 | }); 81 | 82 | observer.observe(target, { 83 | subtree: false, 84 | childList: false, 85 | attributes: true, 86 | attributeFilter: ["data-shader", "data-shader-quality", "data-shader-speed"], 87 | }); 88 | 89 | this.update(); 90 | } 91 | 92 | public loadShader(fragShader: string) { 93 | // Before initializing new shader, remove old ones if present 94 | if (this.pid) this.gl.deleteProgram(this.pid); 95 | if (this.vert) this.gl.deleteShader(this.vert); 96 | if (this.frag) this.gl.deleteShader(this.frag); 97 | 98 | // Create shader program 99 | this.pid = this.gl.createProgram(); 100 | 101 | // Compile vertex shader 102 | this.vert = createShader(this.gl, vertShader, this.gl.VERTEX_SHADER); 103 | this.gl.attachShader(this.pid, this.vert); 104 | 105 | // Compile fragment shader 106 | this.frag = createShader(this.gl, fragShader, this.gl.FRAGMENT_SHADER); 107 | this.gl.attachShader(this.pid, this.frag); 108 | this.gl.linkProgram(this.pid); 109 | this.gl.useProgram(this.pid); 110 | 111 | // Read uniforms 112 | this.uniformResolution = this.gl.getUniformLocation(this.pid, "resolution"); 113 | this.uniformTime = this.gl.getUniformLocation(this.pid, "time"); 114 | this.uniformMouse = this.gl.getUniformLocation(this.pid, "mouse"); 115 | } 116 | 117 | private updateAttributes() { 118 | this.quality = Number(this.target.attributes.getNamedItem("data-shader-quality")?.value) || this.quality; 119 | this.speed = Number(this.target.attributes.getNamedItem("data-shader-speed")?.value) || this.speed; 120 | this.canvas.style.zIndex = this.target.attributes.getNamedItem("data-shader-z-index")?.value || "-1"; 121 | 122 | if (!this.target.attributes.getNamedItem("data-shader")?.value) { 123 | this.destroy(); 124 | } 125 | } 126 | 127 | private resize() { 128 | if (this.target.tagName !== "CANVAS") { 129 | this.canvas.width = window.innerWidth / this.quality; 130 | this.canvas.height = window.innerHeight / this.quality; 131 | } 132 | } 133 | 134 | private mouse(e: MouseEvent) { 135 | this.mouseX = e.clientX; 136 | this.mouseY = e.clientY; 137 | } 138 | 139 | private update() { 140 | const {width, height} = this.canvas; 141 | 142 | if (!this.running) 143 | return; 144 | 145 | this.gl.uniform2f(this.uniformMouse, this.mouseX / width / this.quality, 1 - this.mouseY / height / this.quality); 146 | this.gl.uniform2f(this.uniformResolution, width, height); 147 | this.gl.uniform1f(this.uniformTime, performance.now() / 1000 * this.speed); 148 | 149 | this.gl.viewport(0, 0, width, height); 150 | this.gl.clearColor(0, 0, 0, 0); 151 | this.gl.clear(this.gl.COLOR_BUFFER_BIT); 152 | this.gl.drawArrays(this.gl.TRIANGLES, 0, 3); 153 | 154 | requestAnimationFrame(this.update.bind(this)); 155 | } 156 | 157 | /** 158 | * Returns a WebGLUniformLocation for the given name in the fragment shader. 159 | * @param {string} name 160 | * @returns {WebGLUniformLocation} 161 | */ 162 | public getUniform(name: string): WebGLUniformLocation { 163 | return this.gl.getUniformLocation(this.pid, name); 164 | } 165 | 166 | /** 167 | * This function has to be called to clean up the WebGL context. 168 | * It is also called when the element is removed from the DOM. 169 | * @returns {void} 170 | */ 171 | public destroy(): void { 172 | (this.target as any).shadify = undefined; 173 | 174 | this.running = false; 175 | 176 | this.gl.deleteProgram(this.pid); 177 | this.gl.deleteShader(this.vert); 178 | this.gl.deleteShader(this.frag); 179 | this.canvas.remove(); 180 | 181 | this.pid = null; 182 | this.vert = null; 183 | this.frag = null; 184 | 185 | window.removeEventListener("resize", this.eventHandlerResize); 186 | window.removeEventListener("mousemove", this.eventHandlerMouse); 187 | } 188 | 189 | get quality(): number { 190 | return this._quality; 191 | } 192 | 193 | set quality(value: number) { 194 | this._quality = value; 195 | this.resize(); 196 | } 197 | } 198 | 199 | /** 200 | * Helper function for creating a WebGLShader in Wallpaper class 201 | * @param {WebGLRenderingContext} gl 202 | * @param {string} sourceCode 203 | * @param {number} type 204 | * @returns {WebGLShader} 205 | */ 206 | function createShader(gl: WebGLRenderingContext, sourceCode: string, type: number): WebGLShader { 207 | const shader = gl.createShader(type); 208 | gl.shaderSource(shader, sourceCode); 209 | gl.compileShader(shader); 210 | 211 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 212 | const info = gl.getShaderInfoLog(shader); 213 | throw `Could not compile WebGL program. \n\n${info}`; 214 | } 215 | return shader; 216 | } 217 | 218 | /** 219 | * Fetches the shader code from the given URL. 220 | * If URL is from glslsandbox.com, it will fetch the code from the item page. 221 | * @param {string} url 222 | * @returns {Promise} 223 | */ 224 | async function getCodeFromURL(url: string): Promise { 225 | // link to glslsandbox website is a special case 226 | if (url.includes("glslsandbox.com") && url.includes("/e#")) { 227 | url = url.replace("/e#", "/item/"); 228 | const res = await fetch(url); 229 | const text = await res.json(); 230 | 231 | if (typeof text.code === "undefined") 232 | throw new Error("Could not get shader code"); 233 | 234 | return text.code; 235 | } 236 | 237 | // otherwise we expect a link to a source file and return the code 238 | const res = await fetch(url); 239 | return await res.text(); 240 | } 241 | 242 | /** 243 | * Helper function for handling elements with the "data-shader" attribute. 244 | * @param {HTMLElement} target 245 | * @returns {void} 246 | */ 247 | async function handleHTMLElement(target: HTMLElement): Promise { 248 | if (target == null) 249 | return; 250 | 251 | const url = target.attributes.getNamedItem("data-shader")?.value; 252 | 253 | if (url == null || url.trim().length == 0) 254 | return; 255 | 256 | if ((target as any)?.shadify != null) { 257 | if ((target as any).shadify.target == target) 258 | (target as any).shadify.loadShader(await getCodeFromURL(url)); 259 | else 260 | (target as any).shadify.destroy(); 261 | } else { 262 | console.log(target); 263 | (target as any).shadify = new Wallpaper(await getCodeFromURL(url), target); 264 | } 265 | } 266 | 267 | /** 268 | * Initializes Shadify. 269 | * @returns {void} 270 | */ 271 | function init(): void { 272 | const observer = new MutationObserver((mutationList) => { 273 | console.log(mutationList, mutationList 274 | .filter(x => mutationList.filter(y => y.target == x.target).length >= mutationList.length-1)); 275 | 276 | mutationList 277 | .filter(x => mutationList.filter(y => y.target == x.target).length >= mutationList.length-1) // filter duplicate targets 278 | .forEach((mutation) => handleHTMLElement(mutation.target as HTMLElement)); 279 | }); 280 | 281 | observer.observe(document.body, { 282 | subtree: true, 283 | childList: false, 284 | attributeFilter: ["data-shader"] 285 | }); 286 | 287 | // Handle elements with the "data-shader" attribute 288 | document.querySelectorAll('[data-shader]').forEach((e) => handleHTMLElement(e as HTMLElement)); 289 | 290 | document.removeEventListener("DOMContentLoaded", init); 291 | } 292 | 293 | document.addEventListener("DOMContentLoaded", init); 294 | } 295 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Shadify } from "./Shadify"; 2 | export default Shadify; 3 | Object.assign(module.exports, Shadify); 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "noImplicitAny": true, 5 | "declaration": true, 6 | "module": "commonjs", 7 | "target": "es2015", 8 | "allowJs": true, 9 | "moduleResolution": "node" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var libraryName = 'Shadify'; 2 | var outputFile = libraryName + '.js'; 3 | 4 | module.exports = { 5 | entry: __dirname + '/src/index.ts', 6 | devtool: 'source-map', 7 | output: { 8 | path: __dirname + '/lib', 9 | filename: outputFile, 10 | library: libraryName, 11 | libraryTarget: "var" 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.tsx?$/, 17 | use: 'ts-loader', 18 | exclude: /node_modules/, 19 | }, 20 | ], 21 | }, 22 | resolve: { 23 | extensions: ['.tsx', '.ts', '.js'], 24 | }, 25 | }; 26 | --------------------------------------------------------------------------------