├── .gitignore ├── .idea ├── .gitignore ├── modules.xml ├── php.xml ├── stripe-canvas-gradient.iml └── vcs.xml ├── README.md ├── dist └── stripe-gradient.js ├── index.html ├── package-lock.json ├── package.json ├── src ├── Attribute.js ├── Gradient.js ├── Material.js ├── Mesh.js ├── MiniGL.js ├── PlaneGeometry.js ├── Shaders │ ├── Blend.glsl │ ├── Fragment.glsl │ ├── Noise.glsl │ └── Vertex.glsl ├── ShadersJs │ ├── Blend.js │ ├── Fragment.js │ ├── Noise.js │ └── Vertex.js └── Uniform.js └── webpack.mix.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Project specific 2 | /dist/mix-manifest.json 3 | 4 | # Created by https://www.toptal.com/developers/gitignore/api/node,linux,macos,windows 5 | # Edit at https://www.toptal.com/developers/gitignore?templates=node,linux,macos,windows 6 | 7 | ### Linux ### 8 | *~ 9 | 10 | # temporary files which can be created if a process still has a handle open of a deleted file 11 | .fuse_hidden* 12 | 13 | # KDE directory preferences 14 | .directory 15 | 16 | # Linux trash folder which might appear on any partition or disk 17 | .Trash-* 18 | 19 | # .nfs files are created when an open file is removed but is still being accessed 20 | .nfs* 21 | 22 | ### macOS ### 23 | # General 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must end with two \r 29 | Icon 30 | 31 | 32 | # Thumbnails 33 | ._* 34 | 35 | # Files that might appear in the root of a volume 36 | .DocumentRevisions-V100 37 | .fseventsd 38 | .Spotlight-V100 39 | .TemporaryItems 40 | .Trashes 41 | .VolumeIcon.icns 42 | .com.apple.timemachine.donotpresent 43 | 44 | # Directories potentially created on remote AFP share 45 | .AppleDB 46 | .AppleDesktop 47 | Network Trash Folder 48 | Temporary Items 49 | .apdisk 50 | 51 | ### Node ### 52 | # Logs 53 | logs 54 | *.log 55 | npm-debug.log* 56 | yarn-debug.log* 57 | yarn-error.log* 58 | lerna-debug.log* 59 | .pnpm-debug.log* 60 | 61 | # Diagnostic reports (https://nodejs.org/api/report.html) 62 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 63 | 64 | # Runtime data 65 | pids 66 | *.pid 67 | *.seed 68 | *.pid.lock 69 | 70 | # Directory for instrumented libs generated by jscoverage/JSCover 71 | lib-cov 72 | 73 | # Coverage directory used by tools like istanbul 74 | coverage 75 | *.lcov 76 | 77 | # nyc test coverage 78 | .nyc_output 79 | 80 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 81 | .grunt 82 | 83 | # Bower dependency directory (https://bower.io/) 84 | bower_components 85 | 86 | # node-waf configuration 87 | .lock-wscript 88 | 89 | # Compiled binary addons (https://nodejs.org/api/addons.html) 90 | build/Release 91 | 92 | # Dependency directories 93 | node_modules/ 94 | jspm_packages/ 95 | 96 | # Snowpack dependency directory (https://snowpack.dev/) 97 | web_modules/ 98 | 99 | # TypeScript cache 100 | *.tsbuildinfo 101 | 102 | # Optional npm cache directory 103 | .npm 104 | 105 | # Optional eslint cache 106 | .eslintcache 107 | 108 | # Optional stylelint cache 109 | .stylelintcache 110 | 111 | # Microbundle cache 112 | .rpt2_cache/ 113 | .rts2_cache_cjs/ 114 | .rts2_cache_es/ 115 | .rts2_cache_umd/ 116 | 117 | # Optional REPL history 118 | .node_repl_history 119 | 120 | # Output of 'npm pack' 121 | *.tgz 122 | 123 | # Yarn Integrity file 124 | .yarn-integrity 125 | 126 | # dotenv environment variable files 127 | .env 128 | .env.development.local 129 | .env.test.local 130 | .env.production.local 131 | .env.local 132 | 133 | # parcel-bundler cache (https://parceljs.org/) 134 | .cache 135 | .parcel-cache 136 | 137 | # Next.js build output 138 | .next 139 | out 140 | 141 | # Nuxt.js build / generate output 142 | .nuxt 143 | 144 | # Gatsby files 145 | .cache/ 146 | # Comment in the public line in if your project uses Gatsby and not Next.js 147 | # https://nextjs.org/blog/next-9-1#public-directory-support 148 | # public 149 | 150 | # vuepress build output 151 | .vuepress/dist 152 | 153 | # vuepress v2.x temp and cache directory 154 | .temp 155 | 156 | # Docusaurus cache and generated files 157 | .docusaurus 158 | 159 | # Serverless directories 160 | .serverless/ 161 | 162 | # FuseBox cache 163 | .fusebox/ 164 | 165 | # DynamoDB Local files 166 | .dynamodb/ 167 | 168 | # TernJS port file 169 | .tern-port 170 | 171 | # Stores VSCode versions used for testing VSCode extensions 172 | .vscode-test 173 | 174 | # yarn v2 175 | .yarn/cache 176 | .yarn/unplugged 177 | .yarn/build-state.yml 178 | .yarn/install-state.gz 179 | .pnp.* 180 | 181 | ### Node Patch ### 182 | # Serverless Webpack directories 183 | .webpack/ 184 | 185 | # Optional stylelint cache 186 | 187 | # SvelteKit build / generate output 188 | .svelte-kit 189 | 190 | ### Windows ### 191 | # Windows thumbnail cache files 192 | Thumbs.db 193 | Thumbs.db:encryptable 194 | ehthumbs.db 195 | ehthumbs_vista.db 196 | 197 | # Dump file 198 | *.stackdump 199 | 200 | # Folder config file 201 | [Dd]esktop.ini 202 | 203 | # Recycle Bin used on file shares 204 | $RECYCLE.BIN/ 205 | 206 | # Windows Installer files 207 | *.cab 208 | *.msi 209 | *.msix 210 | *.msm 211 | *.msp 212 | 213 | # Windows shortcuts 214 | *.lnk 215 | 216 | # End of https://www.toptal.com/developers/gitignore/api/node,linux,macos,windows 217 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/php.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/stripe-canvas-gradient.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stripe Gradient 2 | 3 | A reverse engineered, and simplified javascript library to replicate the animated [Stripe](https://stripe.com/) gradients. 4 | 5 | ## Basic usage 6 | 7 | **HTML** 8 | 9 | ```html 10 | 11 | ``` 12 | 13 | **JavasScript** 14 | ```javascript 15 | new Gradient({ 16 | canvas: '#my-canvas-id', 17 | colors: ['#a960ee', '#ff333d', '#90e0ff', '#ffcb57'] 18 | }); 19 | ``` 20 | 21 | 22 | ## jQuery ready 23 | 24 | ```javascript 25 | $('#my-canvas-id').gradient({ 26 | colors: ['#a960ee', '#ff333d', '#90e0ff', '#ffcb57'] 27 | }); 28 | ``` 29 | -------------------------------------------------------------------------------- /dist/stripe-gradient.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";function e(e,n){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null==n)return;var i,r,a=[],o=!0,s=!1;try{for(n=n.call(e);!(o=(i=n.next()).done)&&(a.push(i.value),!t||a.length!==t);o=!0);}catch(e){s=!0,r=e}finally{try{o||null==n.return||n.return()}finally{if(s)throw r}}return a}(e,n)||function(e,n){if(!e)return;if("string"==typeof e)return t(e,n);var i=Object.prototype.toString.call(e).slice(8,-1);"Object"===i&&e.constructor&&(i=e.constructor.name);if("Map"===i||"Set"===i)return Array.from(e);if("Arguments"===i||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(i))return t(e,n)}(e,n)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function t(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,i=new Array(t);n3&&void 0!==arguments[3]?arguments[3]:{};n(this,t),r(this,"gl",void 0),r(this,"type",void 0),r(this,"value",void 0),r(this,"typeFn",void 0),r(this,"_typeMap",{float:"1f",int:"1i",vec2:"2fv",vec3:"3fv",vec4:"4fv",mat4:"Matrix4fv"}),Object.assign(this,o),this.gl=e,this.type=i,this.value=a,this.typeFn=this._typeMap[this.type]||this._typeMap.float,this.update()}var a,o,s;return a=t,(o=[{key:"update",value:function(e){if(this.value){var t=this.value,n=null;0===this.typeFn.indexOf("Matrix")&&(t=this.transpose,n=this.value),this.gl.getContext()["uniform".concat(this.typeFn)](e,t,n)}}},{key:"getDeclaration",value:function(t,n,i){if(this.excludeFrom!==n){if("array"===this.type)return"".concat(this.value[0].getDeclaration(t,n,this.value.length),"\nconst int ").concat(t,"_length = ").concat(this.value.length,";");if("struct"===this.type){var r=t.replace("u_","");r=r.charAt(0).toUpperCase()+r.slice(1);var a=Object.entries(this.value).map((function(t){var i=e(t,2),r=i[0];return i[1].getDeclaration(r,n).replace(/^uniform/,"")})).join("");return"uniform struct ".concat(r," {\n ").concat(a,"\n} ").concat(t).concat(i>0?"[".concat(i,"]"):"",";")}return"uniform ".concat(this.type," ").concat(t).concat(i>0?"[".concat(i,"]"):"",";")}}}])&&i(a.prototype,o),s&&i(a,s),Object.defineProperty(a,"prototype",{writable:!1}),t}();function o(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:640,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:480;this.getCanvas().width=e,this.getCanvas().height=t,this.getContext().viewport(0,0,e,t),this.commonUniforms.resolution.value=[e,t],this.commonUniforms.aspectRatio.value=e/t}},{key:"setOrthographicCamera",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:-2e3,r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:2e3;this.commonUniforms.projectionMatrix.value=[2/this.getCanvas().width,0,0,0,0,2/this.getCanvas().height,0,0,0,0,2/(i-r),0,e,t,n,1]}},{key:"render",value:function(){this.getContext().clearColor(0,0,0,0),this.getContext().clearDepth(1),this.meshes.forEach((function(e){e.draw()}))}}],n&&o(t.prototype,n),i&&o(t,i),Object.defineProperty(t,"prototype",{writable:!1}),e}();function c(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null==n)return;var i,r,a=[],o=!0,s=!1;try{for(n=n.call(e);!(o=(i=n.next()).done)&&(a.push(i.value),!t||a.length!==t);o=!0);}catch(e){s=!0,r=e}finally{try{o||null==n.return||n.return()}finally{if(s)throw r}}return a}(e,t)||function(e,t){if(!e)return;if("string"==typeof e)return u(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return u(e,t)}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function u(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,i=new Array(t);n3&&void 0!==arguments[3]?arguments[3]:{},a=arguments.length>4&&void 0!==arguments[4]?arguments[4]:{};h(this,e),b(this,"gl",void 0),b(this,"uniformInstances",[]),Object.assign(this,a),this.gl=t,this.uniforms=r;var o=this.gl.getContext(),s="\n precision highp float;\n ";this.vertexSource="\n ".concat(s,"\n attribute vec4 position;\n attribute vec2 uv;\n attribute vec2 uvNorm;\n ").concat(this._getUniformVariableDeclarations(this.gl.commonUniforms,"vertex"),"\n ").concat(this._getUniformVariableDeclarations(r,"vertex"),"\n ").concat(n,"\n "),this.Source="\n ".concat(s,"\n ").concat(this._getUniformVariableDeclarations(this.gl.commonUniforms,"fragment"),"\n ").concat(this._getUniformVariableDeclarations(r,"fragment"),"\n ").concat(i,"\n "),this.vertexShader=this._getShaderByType(o.VERTEX_SHADER,this.vertexSource),this.fragmentShader=this._getShaderByType(o.FRAGMENT_SHADER,this.Source),this.program=o.createProgram(),o.attachShader(this.program,this.vertexShader),o.attachShader(this.program,this.fragmentShader),o.linkProgram(this.program),o.getProgramParameter(this.program,o.LINK_STATUS)||console.error(o.getProgramInfoLog(this.program)),o.useProgram(this.program),this.attachUniforms(void 0,this.gl.commonUniforms),this.attachUniforms(void 0,this.uniforms)}var t,n,i;return t=e,(n=[{key:"_getShaderByType",value:function(e,t){var n=this.gl.getContext(),i=n.createShader(e);return n.shaderSource(i,t),n.compileShader(i),n.getShaderParameter(i,n.COMPILE_STATUS)||console.error(n.getShaderInfoLog(i)),i}},{key:"_getUniformVariableDeclarations",value:function(e,t){return Object.entries(e).map((function(e){var n=c(e,2),i=n[0];return n[1].getDeclaration(i,t)})).join("\n")}},{key:"attachUniforms",value:function(e,t){var n=this;e?"array"===t.type?t.value.forEach((function(t,i){n.attachUniforms("".concat(e,"[").concat(i,"]"),t)})):"struct"===t.type?Object.entries(t.value).forEach((function(t){var i=c(t,2),r=i[0],a=i[1];n.attachUniforms("".concat(e,".").concat(r),a)})):this.uniformInstances.push({uniform:t,location:this.gl.getContext().getUniformLocation(this.program,e)}):Object.entries(t).forEach((function(e){var t=c(e,2),i=t[0],r=t[1];n.attachUniforms(i,r)}))}}])&&v(t.prototype,n),i&&v(t,i),Object.defineProperty(t,"prototype",{writable:!1}),e}();function f(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null==n)return;var i,r,a=[],o=!0,s=!1;try{for(n=n.call(e);!(o=(i=n.next()).done)&&(a.push(i.value),!t||a.length!==t);o=!0);}catch(e){s=!0,r=e}finally{try{o||null==n.return||n.return()}finally{if(s)throw r}}return a}(e,t)||function(e,t){if(!e)return;if("string"==typeof e)return g(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return g(e,t)}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function g(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,i=new Array(t);n3&&void 0!==arguments[3]?arguments[3]:{};m(this,e),p(this,"gl",void 0),p(this,"wireframe",!1),p(this,"attributeInstances",[]),Object.assign(this,a),this.geometry=n,this.material=i,this.gl=t,Object.entries(this.geometry.attributes).forEach((function(e){var t=f(e,2),n=t[0],i=t[1];r.attributeInstances.push({attribute:i,location:i.attach(n,r.material.program)})})),this.gl.meshes.push(this)}var t,n,i;return t=e,(n=[{key:"draw",value:function(){var e=this.gl.getContext();e.useProgram(this.material.program),this.material.uniformInstances.forEach((function(e){var t=e.uniform,n=e.location;t.update(n)})),this.attributeInstances.forEach((function(e){var t=e.attribute,n=e.location;t.use(n)}));var t=this.wireframe?e.LINES:e.TRIANGLES;e.drawElements(t,this.geometry.attributes.index.values.length,e.UNSIGNED_SHORT,0)}},{key:"remove",value:function(){var e=this;this.gl.meshes=this.gl.meshes.filter((function(t){return t!=e}))}}])&&y(t.prototype,n),i&&y(t,i),Object.defineProperty(t,"prototype",{writable:!1}),e}();function w(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function C(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:{};w(this,e),_(this,"gl",void 0),_(this,"type",void 0),_(this,"buffer",void 0),_(this,"normalized",!1),Object.assign(this,n),this.gl=t,this.type=this.gl.getContext().FLOAT,this.buffer=this.gl.getContext().createBuffer(),this.update()}var t,n,i;return t=e,(n=[{key:"update",value:function(){if(this.values){var e=this.gl.getContext();e.bindBuffer(this.target,this.buffer),e.bufferData(this.target,this.values,e.STATIC_DRAW)}}},{key:"attach",value:function(e,t){var n=this.gl.getContext(),i=n.getAttribLocation(t,e);return this.target===n.ARRAY_BUFFER&&(n.enableVertexAttribArray(i),n.vertexAttribPointer(i,this.size,this.type,this.normalized,0,0)),i}},{key:"use",value:function(e){var t=this.gl.getContext();t.bindBuffer(this.target,this.buffer),this.target===t.ARRAY_BUFFER&&(t.enableVertexAttribArray(e),t.vertexAttribPointer(e,this.size,this.type,this.normalized,0,0))}}])&&C(t.prototype,n),i&&C(t,i),Object.defineProperty(t,"prototype",{writable:!1}),e}();function L(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function O(e,t){for(var n=0;n6&&void 0!==arguments[6]?arguments[6]:{};L(this,e),A(this,"gl",void 0),A(this,"attributes",void 0),Object.assign(this,s),this.gl=t;var l=this.gl.getContext();l.createBuffer(),this.attributes={position:new S(this.gl,{target:l.ARRAY_BUFFER,size:3}),uv:new S(this.gl,{target:l.ARRAY_BUFFER,size:2}),uvNorm:new S(this.gl,{target:l.ARRAY_BUFFER,size:2}),index:new S(this.gl,{target:l.ELEMENT_ARRAY_BUFFER,size:3,type:l.UNSIGNED_SHORT})},this.setTopology(r,a),this.setSize(n,i,o)}var t,n,i;return t=e,n=[{key:"setTopology",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1;this.xSegCount=e,this.ySegCount=t,this.vertexCount=(this.xSegCount+1)*(this.ySegCount+1),this.quadCount=this.xSegCount*this.ySegCount*2,this.attributes.uv.values=new Float32Array(2*this.vertexCount),this.attributes.uvNorm.values=new Float32Array(2*this.vertexCount),this.attributes.index.values=new Uint16Array(3*this.quadCount);for(var n=0;n<=this.ySegCount;n++)for(var i=0;i<=this.xSegCount;i++){var r=n*(this.xSegCount+1)+i;if(this.attributes.uv.values[2*r]=i/this.xSegCount,this.attributes.uv.values[2*r+1]=1-n/this.ySegCount,this.attributes.uvNorm.values[2*r]=i/this.xSegCount*2-1,this.attributes.uvNorm.values[2*r+1]=1-n/this.ySegCount*2,i0&&void 0!==arguments[0]?arguments[0]:1,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"xz";this.width=e,this.height=t,this.orientation=n,this.attributes.position.values&&this.attributes.position.values.length===3*this.vertexCount||(this.attributes.position.values=new Float32Array(3*this.vertexCount));for(var i=e/-2,r=t/-2,a=e/this.xSegCount,o=t/this.ySegCount,s=0;s<=this.ySegCount;s++)for(var l=r+s*o,c=0;c<=this.xSegCount;c++){var u=i+c*a,h=s*(this.xSegCount+1)+c;this.attributes.position.values[3*h+"xyz".indexOf(n[0])]=u,this.attributes.position.values[3*h+"xyz".indexOf(n[1])]=-l}this.attributes.position.update()}}],n&&O(t.prototype,n),i&&O(t,i),Object.defineProperty(t,"prototype",{writable:!1}),e}();function j(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null==n)return;var i,r,a=[],o=!0,s=!1;try{for(n=n.call(e);!(o=(i=n.next()).done)&&(a.push(i.value),!t||a.length!==t);o=!0);}catch(e){s=!0,r=e}finally{try{o||null==n.return||n.return()}finally{if(s)throw r}}return a}(e,t)||function(e,t){if(!e)return;if("string"==typeof e)return z(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return z(e,t)}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function z(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,i=new Array(t);n1&&void 0!==arguments[1]?arguments[1]:void 0;return void 0===t&&e in this._class.defaultOptions&&(t=this._class.defaultOptions[e]),e in this.options?this.options[e]:t}},{key:"findCanvas",value:function(e){var t="string"==typeof e?document.querySelector(e):e;return t instanceof HTMLCanvasElement?t:null}},{key:"setCanvas",value:function(e){e?(this._canvas=e,this._context=e.getContext("webgl",{antialias:!0})):(this._canvas=null,this._context=null)}},{key:"getCanvas",value:function(){return this._canvas}},{key:"getContext",value:function(){return this._context}},{key:"setFlag",value:function(e,t){return this._flags[e]=t}},{key:"getFlag",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:void 0;return this._flags[e]||t}},{key:"handleScroll",value:function(){clearTimeout(this.scrollingTimeout),this.scrollingTimeout=setTimeout(this.handleScrollEnd,this.scrollingRefreshDelay),this.getFlag("playing")&&(this.setFlag("isScrolling",!0),this.pause())}},{key:"handleScrollEnd",value:function(){this.setFlag("isScrolling",!1),this.getFlag("isIntersecting")&&this.play()}},{key:"resize",value:function(){var e=j(this.getOption("density"),2),t=e[0],n=e[1];this.width=window.innerWidth,this._minigl.setSize(this.width,this.height),this._minigl.setOrthographicCamera(),this.xSegCount=Math.ceil(this.width*t),this.ySegCount=Math.ceil(this.height*n),this.mesh.geometry.setTopology(this.xSegCount,this.ySegCount),this.mesh.geometry.setSize(this.width,this.height),this.mesh.material.uniforms.u_shadow_power.value=this.width<600?5:6}},{key:"animate",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,t=!!window.document.hidden||!this.getFlag("playing")||parseInt(e,10)%2==0,n=this.getFlag("lastFrame",0);if(t||(this.time+=Math.min(e-n,1e3/15),n=this.setFlag("lastFrame",e),this.mesh.material.uniforms.u_time.value=this.time,this._minigl.render()),0!==n&&this.getOption("static"))return this._minigl.render(),this.disconnect();this.getFlag("playing")&&requestAnimationFrame(this.animate.bind(this))}},{key:"pause",value:function(){this.setFlag("playing",!1)}},{key:"play",value:function(){requestAnimationFrame(this.animate.bind(this)),this.setFlag("playing",!0)}},{key:"disconnect",value:function(){this.scrollObserver&&(window.removeEventListener("scroll",this.handleScroll),this.scrollObserver.disconnect()),window.removeEventListener("resize",this.resize)}},{key:"initMaterial",value:function(){var e=this.getOption("colors").map((function(e){if(4===e.length){var t=e.substr(1).split("").map((function(e){return e+e})).join("");e="#".concat(t)}return e&&"0x".concat(e.substr(1))})).filter(Boolean).map(this.normalizeColor);this.uniforms={u_time:new a(this._minigl,"float",0),u_shadow_power:new a(this._minigl,"float",10),u_darken_top:new a(this._minigl,"float",this.getCanvas().dataset.jsDarkenTop?1:0),u_active_colors:new a(this._minigl,"vec4",this.activeColors),u_global:new a(this._minigl,"struct",{noiseFreq:new a(this._minigl,"vec2",[this.freqX,this.freqY]),noiseSpeed:new a(this._minigl,"float",5e-6)}),u_vertDeform:new a(this._minigl,"struct",{incline:new a(this._minigl,"float",Math.sin(this.getOption("angle"))/Math.cos(this.getOption("angle"))),offsetTop:new a(this._minigl,"float",-.5),offsetBottom:new a(this._minigl,"float",-.5),noiseFreq:new a(this._minigl,"vec2",[3,4]),noiseAmp:new a(this._minigl,"float",this.getOption("amplitude")),noiseSpeed:new a(this._minigl,"float",10),noiseFlow:new a(this._minigl,"float",3),noiseSeed:new a(this._minigl,"float",this.seed)},{excludeFrom:"fragment"}),u_baseColor:new a(this._minigl,"vec3",e[0],{excludeFrom:"fragment"}),u_waveLayers:new a(this._minigl,"array",[],{excludeFrom:"fragment"})};for(var t=1;t>16&255)/255,(e>>8&255)/255,(255&e)/255]}}],n&&D(t.prototype,n),i&&D(t,i),Object.defineProperty(t,"prototype",{writable:!1}),e}();k(E,"defaultOptions",{canvas:null,colors:["#f00","#0f0","#00f"],wireframe:!1,density:[.06,.16],angle:0,amplitude:320,static:!1,loadedClass:"is-loaded",zoom:1,speed:5,rotation:0}),window.Gradient=E,window.jQuery&&(jQuery.fn.gradient=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return e.canvas=this.get(0),this._gradient=new E(e),this})}(); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stripe Gradient Animation 7 | 8 | 19 | 20 | 21 | 22 | 23 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@thelevicole/stripe-gradient", 3 | "version": "1.0.0", 4 | "description": "A reverse engineered, and simplified javascript library to replicate the animated Stripe gradients.", 5 | "main": "dist/stripe-gradient.js", 6 | "keywords": [ 7 | "stripe", 8 | "gradient", 9 | "canvas", 10 | "animation" 11 | ], 12 | "browserslist": [ 13 | "last 3 version", 14 | "> 1%", 15 | "ie 11" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/thelevicole/stripe-gradient.git" 20 | }, 21 | "author": { 22 | "name": "Levi Cole", 23 | "email": "dev@thelevicole.com" 24 | }, 25 | "bugs": { 26 | "url": "https://github.com/thelevicole/stripe-gradient/issues" 27 | }, 28 | "homepage": "https://thelevicole.com/stripe-gradient/", 29 | "devDependencies": { 30 | "laravel-mix": "^6.0.43" 31 | }, 32 | "scripts": { 33 | "build": "mix --production", 34 | "watch": "mix --production -- --watch --progress" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Attribute.js: -------------------------------------------------------------------------------- 1 | export default class Attribute { 2 | 3 | /** 4 | * The parent MiniGL controller. 5 | * 6 | * @type {MiniGL} 7 | * @private 8 | */ 9 | gl; 10 | 11 | type; 12 | 13 | buffer; 14 | 15 | normalized = false; 16 | 17 | /** 18 | * @param {MiniGL} minigl 19 | * @param {object} properties 20 | */ 21 | constructor(minigl, properties = {}) { 22 | 23 | // Add additional properties. 24 | Object.assign(this, properties); 25 | 26 | // Set required properties. 27 | this.gl = minigl; 28 | this.type = this.gl.getContext().FLOAT; 29 | this.buffer = this.gl.getContext().createBuffer(); 30 | 31 | this.update(); 32 | } 33 | 34 | update() { 35 | if (this.values) { 36 | const context = this.gl.getContext(); 37 | context.bindBuffer(this.target, this.buffer); 38 | context.bufferData(this.target, this.values, context.STATIC_DRAW); 39 | } 40 | } 41 | 42 | attach(e, t) { 43 | const context = this.gl.getContext(); 44 | const n = context.getAttribLocation(t, e); 45 | 46 | if (this.target === context.ARRAY_BUFFER) { 47 | context.enableVertexAttribArray(n); 48 | context.vertexAttribPointer(n, this.size, this.type, this.normalized, 0, 0); 49 | } 50 | 51 | return n; 52 | } 53 | 54 | use(e) { 55 | const context = this.gl.getContext(); 56 | context.bindBuffer(this.target, this.buffer); 57 | if (this.target === context.ARRAY_BUFFER) { 58 | context.enableVertexAttribArray(e); 59 | context.vertexAttribPointer(e, this.size, this.type, this.normalized, 0, 0); 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/Gradient.js: -------------------------------------------------------------------------------- 1 | import MiniGL from './MiniGL.js'; 2 | import Blend from './ShadersJs/Blend.js'; 3 | import Fragment from './ShadersJs/Fragment.js'; 4 | import Noise from './ShadersJs/Noise.js'; 5 | import Vertex from './ShadersJs/Vertex.js'; 6 | import Uniform from './Uniform.js'; 7 | import Material from './Material.js'; 8 | import Mesh from './Mesh.js'; 9 | import PlaneGeometry from './PlaneGeometry.js'; 10 | 11 | class Gradient { 12 | 13 | /** 14 | * Class reference used primarily for static properties. 15 | * 16 | * @type {Gradient} 17 | */ 18 | _class = Gradient; 19 | 20 | /** 21 | * Default options used if user doesn't provide a value. 22 | * 23 | * @type {object} 24 | */ 25 | static defaultOptions = { 26 | canvas: null, 27 | colors: ['#f00', '#0f0', '#00f'], 28 | wireframe: false, 29 | density: [.06, .16], 30 | 31 | angle: 0, 32 | amplitude: 320, 33 | static: false, // Enable non-animating gradient 34 | 35 | loadedClass: 'is-loaded', 36 | 37 | zoom: 1, // @todo not used. 38 | speed: 5, // @todo not used. 39 | rotation: 0, // @todo not used. 40 | }; 41 | 42 | vertexShader = null; 43 | uniforms = null; 44 | time = 1253106; // @todo work out why this number has been choosen. 45 | mesh = null; 46 | material = null; 47 | geometry = null; 48 | 49 | // @todo tidy up these properties 50 | scrollingTimeout = null; 51 | scrollingRefreshDelay = 200; 52 | scrollObserver = null; 53 | width = null; 54 | minWidth = 1111; 55 | height = 600; 56 | xSegCount = null; 57 | ySegCount = null; 58 | freqX = 0.00014; 59 | freqY = 0.00029; 60 | freqDelta = 0.00001; 61 | activeColors = [1, 1, 1, 1]; 62 | 63 | /** 64 | * @type {{fragment: string, vertex: string, blend: string, noise: string}} 65 | */ 66 | shaderFiles = { 67 | vertex: Vertex, 68 | noise: Noise, 69 | blend: Blend, 70 | fragment: Fragment 71 | }; 72 | 73 | /** 74 | * User defined options 75 | * 76 | * @type {object} 77 | */ 78 | options = {}; 79 | 80 | /** 81 | * Store arbitrary flags consisting of mainly boolean values but in some cases can be any data type. 82 | * @type {object} 83 | * @private 84 | */ 85 | _flags = { 86 | playing: true // autoplay on init 87 | }; 88 | 89 | /** 90 | * Cached canvas element. 91 | * 92 | * @type {HTMLCanvasElement|null} 93 | * @private 94 | */ 95 | _canvas; 96 | 97 | /** 98 | * @type {WebGLRenderingContext|null} 99 | * @private 100 | */ 101 | _context; 102 | 103 | /** 104 | * Cached MiniGL instance. 105 | * 106 | * @type {MiniGL} 107 | * @private 108 | */ 109 | _minigl; 110 | 111 | /** 112 | * @param {object} options 113 | */ 114 | constructor(options) { 115 | this.options = options; 116 | 117 | // Find and store the canvas element. 118 | this.setCanvas(this.findCanvas(this.getOption('canvas'))); 119 | 120 | // Bail if no canvas element. 121 | if (!this.getCanvas()) { 122 | throw 'Missing Canvas. Pass the canvas to the Gradient constructor.'; 123 | } 124 | 125 | // Initiate the MiniGL controller. 126 | this._minigl = new MiniGL(this.getCanvas(), this.getCanvas().offsetWidth, this.getCanvas().offsetHeight); 127 | 128 | // Initiate the canvas gradient. 129 | this.init(); 130 | } 131 | 132 | /** 133 | * Get a user or default option. 134 | * 135 | * @param {string} name 136 | * @param defaultValue 137 | * @returns {*} 138 | */ 139 | getOption(name, defaultValue = undefined) { 140 | if (defaultValue === undefined && name in this._class.defaultOptions) { 141 | defaultValue = this._class.defaultOptions[name]; 142 | } 143 | 144 | return name in this.options ? this.options[name] : defaultValue; 145 | } 146 | 147 | /** 148 | * Get the canvas element and cache as private property. 149 | * 150 | * @param {string|HTMLCanvasElement} selector 151 | * @returns {HTMLCanvasElement|null} 152 | */ 153 | findCanvas(selector) { 154 | const canvas = typeof selector === 'string' ? document.querySelector(selector) : selector; 155 | 156 | if (canvas instanceof HTMLCanvasElement) { 157 | return canvas; 158 | } 159 | 160 | return null; 161 | } 162 | 163 | /** 164 | * Sets the `_canvas` and `_context` properties. 165 | * 166 | * @param {HTMLCanvasElement} canvas 167 | */ 168 | setCanvas(canvas) { 169 | if (canvas) { 170 | this._canvas = canvas; 171 | this._context = canvas.getContext('webgl', { 172 | antialias: true 173 | }); 174 | } else { 175 | this._canvas = null; 176 | this._context = null; 177 | } 178 | } 179 | 180 | /** 181 | * @return {HTMLCanvasElement} 182 | */ 183 | getCanvas() { 184 | return this._canvas; 185 | } 186 | 187 | /** 188 | * @return {WebGLRenderingContext} 189 | */ 190 | getContext() { 191 | return this._context; 192 | } 193 | 194 | /** 195 | * @param {string} name 196 | * @param {*} value 197 | * @return {*} 198 | */ 199 | setFlag(name, value) { 200 | return this._flags[name] = value; 201 | } 202 | 203 | /** 204 | * @param {string} name 205 | * @param defaultValue 206 | * @return {boolean|undefined} 207 | */ 208 | getFlag(name, defaultValue = undefined) { 209 | return this._flags[name] || defaultValue; 210 | } 211 | 212 | handleScroll() { 213 | clearTimeout(this.scrollingTimeout); 214 | this.scrollingTimeout = setTimeout(this.handleScrollEnd, this.scrollingRefreshDelay); 215 | 216 | if (this.getFlag('playing')) { 217 | this.setFlag('isScrolling', true); 218 | this.pause(); 219 | } 220 | } 221 | 222 | handleScrollEnd() { 223 | this.setFlag('isScrolling', false); 224 | 225 | if (this.getFlag('isIntersecting')) { 226 | this.play(); 227 | } 228 | } 229 | 230 | /** 231 | * @todo Update resize method to use canvas size not window. 232 | */ 233 | resize() { 234 | const [ densityX, densityY ] = this.getOption('density'); 235 | this.width = window.innerWidth; 236 | this._minigl.setSize(this.width, this.height); 237 | this._minigl.setOrthographicCamera(); 238 | this.xSegCount = Math.ceil(this.width * densityX); 239 | this.ySegCount = Math.ceil(this.height * densityY); 240 | this.mesh.geometry.setTopology(this.xSegCount, this.ySegCount); 241 | this.mesh.geometry.setSize(this.width, this.height); 242 | this.mesh.material.uniforms.u_shadow_power.value = this.width < 600 ? 5 : 6; 243 | } 244 | 245 | animate(event = 0) { 246 | const shouldSkipFrame = !!window.document.hidden || (!this.getFlag('playing') || parseInt(event, 10) % 2 === 0); 247 | let lastFrame = this.getFlag('lastFrame', 0); 248 | 249 | if (!shouldSkipFrame) { 250 | this.time += Math.min(event - lastFrame, 1000 / 15); 251 | lastFrame = this.setFlag('lastFrame', event); 252 | this.mesh.material.uniforms.u_time.value = this.time; 253 | this._minigl.render(); 254 | } 255 | 256 | // @todo support static gradient. 257 | if (lastFrame !== 0 && this.getOption('static')) { 258 | this._minigl.render(); 259 | return this.disconnect(); 260 | } 261 | 262 | if (/*this.getFlag('isIntersecting') && */this.getFlag('playing')) { 263 | requestAnimationFrame(this.animate.bind(this)); 264 | } 265 | } 266 | 267 | /** 268 | * Pause the animation. 269 | */ 270 | pause() { 271 | this.setFlag('playing', false); 272 | } 273 | 274 | /** 275 | * Start or continue the animation. 276 | */ 277 | play() { 278 | requestAnimationFrame(this.animate.bind(this)); 279 | this.setFlag('playing', true); 280 | } 281 | 282 | disconnect() { 283 | if (this.scrollObserver) { 284 | window.removeEventListener("scroll", this.handleScroll); 285 | this.scrollObserver.disconnect(); 286 | } 287 | 288 | window.removeEventListener("resize", this.resize); 289 | } 290 | 291 | initMaterial() { 292 | 293 | /** 294 | * @type {array[]} 295 | */ 296 | const colors = this.getOption('colors').map(hex => { 297 | 298 | // Check if shorthand hex value was used and double the length so the conversion in normalizeColor will work. 299 | if (hex.length === 4) { 300 | const hexTemp = hex.substr(1).split('').map(hexTemp => hexTemp + hexTemp).join(''); 301 | hex = `#${hexTemp}` 302 | } 303 | 304 | return hex && `0x${hex.substr(1)}`; 305 | }).filter(Boolean).map(this.normalizeColor); 306 | 307 | this.uniforms = { 308 | u_time: new Uniform(this._minigl, 'float',0), 309 | u_shadow_power: new Uniform(this._minigl, 'float', 10), 310 | u_darken_top: new Uniform(this._minigl, 'float', this.getCanvas().dataset.jsDarkenTop ? 1 : 0), 311 | u_active_colors: new Uniform(this._minigl, 'vec4', this.activeColors), 312 | 313 | u_global: new Uniform(this._minigl, 'struct', { 314 | noiseFreq: new Uniform(this._minigl, 'vec2', [this.freqX, this.freqY]), 315 | noiseSpeed: new Uniform(this._minigl, 'float',0.000005) 316 | }), 317 | 318 | u_vertDeform: new Uniform(this._minigl, 'struct', { 319 | incline: new Uniform(this._minigl, 'float', Math.sin(this.getOption('angle')) / Math.cos(this.getOption('angle'))), 320 | offsetTop: new Uniform(this._minigl, 'float', -0.5), 321 | offsetBottom: new Uniform(this._minigl, 'float', -0.5), 322 | noiseFreq: new Uniform(this._minigl, 'vec2', [3, 4]), 323 | noiseAmp: new Uniform(this._minigl, 'float', this.getOption('amplitude')), 324 | noiseSpeed: new Uniform(this._minigl, 'float', 10), 325 | noiseFlow: new Uniform(this._minigl, 'float', 3), 326 | noiseSeed: new Uniform(this._minigl, 'float', this.seed) 327 | }, { 328 | excludeFrom: 'fragment' 329 | }), 330 | 331 | u_baseColor: new Uniform(this._minigl, 'vec3', colors[0], { 332 | excludeFrom: 'fragment' 333 | }), 334 | 335 | u_waveLayers: new Uniform(this._minigl, 'array', [], { 336 | excludeFrom: 'fragment' 337 | }) 338 | }; 339 | 340 | for (let e = 1; e < colors.length; e += 1) { 341 | const waveLayerUniform = new Uniform(this._minigl, 'struct', { 342 | color: new Uniform(this._minigl, 'vec3', colors[e]), 343 | noiseFreq: new Uniform(this._minigl, 'vec2', [2 + e / colors.length, 3 + e / colors.length]), 344 | noiseSpeed: new Uniform(this._minigl, 'float', 11 + 0.3 * e), 345 | noiseFlow: new Uniform(this._minigl, 'float', 6.5 + 0.3 * e), 346 | noiseSeed: new Uniform(this._minigl, 'float', this.seed + 10 * e), 347 | noiseFloor: new Uniform(this._minigl, 'float', 0.1), 348 | noiseCeil: new Uniform(this._minigl, 'float', 0.63 + 0.07 * e) 349 | }); 350 | 351 | this.uniforms.u_waveLayers.value.push(waveLayerUniform); 352 | } 353 | this.vertexShader = [ 354 | this.shaderFiles.noise, 355 | this.shaderFiles.blend, 356 | this.shaderFiles.vertex 357 | ].join("\n\n"); 358 | 359 | return new Material(this._minigl, this.vertexShader, this.shaderFiles.fragment, this.uniforms); 360 | } 361 | 362 | initMesh() { 363 | this.material = this.initMaterial(); 364 | this.geometry = new PlaneGeometry(this._minigl); 365 | 366 | this.mesh = new Mesh(this._minigl, this.geometry, this.material); 367 | this.mesh.wireframe = this.getOption('wireframe'); 368 | } 369 | 370 | updateFrequency(e) { 371 | this.freqX += e; 372 | this.freqY += e; 373 | } 374 | 375 | toggleColor(index) { 376 | this.activeColors[index] = this.activeColors[index] === 0 ? 1 : 0; 377 | } 378 | 379 | init() { 380 | 381 | // Add loaded class. 382 | const loadedClass = this.getOption('loadedClass'); 383 | if (loadedClass) { 384 | this.getCanvas().classList.add(loadedClass); 385 | } 386 | 387 | // @todo add scroll observer. 388 | // 389 | // this.scrollObserver = await s.create(.1, false), 390 | // this.scrollObserver.observe(this.getCanvas()); 391 | // 392 | // this.scrollObserver.onSeparate(() => { 393 | // window.removeEventListener("scroll", this.handleScroll); 394 | // 395 | // this.setFlag('isIntersecting', false); 396 | // 397 | // if (this.getFlag('playing')) { 398 | // this.pause(); 399 | // } 400 | // }); 401 | // 402 | // this.scrollObserver.onIntersect(() => { 403 | // window.addEventListener("scroll", this.handleScroll); 404 | // 405 | // this.setFlag('isIntersecting', true); 406 | // 407 | // this.addIsLoadedClass(); 408 | // this.play(); 409 | // }); 410 | 411 | this.initMesh(); 412 | this.resize(); 413 | requestAnimationFrame(this.animate.bind(this)); 414 | window.addEventListener('resize', this.resize); 415 | } 416 | 417 | /** 418 | * @param {string} hexCode 419 | * @return {number[]} 420 | */ 421 | normalizeColor(hexCode) { 422 | return [ 423 | (hexCode >> 16 & 255) / 255, 424 | (hexCode >> 8 & 255) / 255, 425 | (255 & hexCode) / 255 426 | ]; 427 | } 428 | 429 | } 430 | 431 | window.Gradient = Gradient; 432 | 433 | if (window.jQuery) { 434 | jQuery.fn.gradient = function(options = {}) { 435 | options.canvas = this.get(0); 436 | this._gradient = new Gradient(options); 437 | return this; 438 | }; 439 | } 440 | -------------------------------------------------------------------------------- /src/Material.js: -------------------------------------------------------------------------------- 1 | import Attribute from './Attribute.js'; 2 | 3 | export default class Material { 4 | 5 | /** 6 | * The parent MiniGL controller. 7 | * 8 | * @type {MiniGL} 9 | * @private 10 | */ 11 | gl; 12 | 13 | uniformInstances = []; 14 | 15 | /** 16 | * 17 | * @param {MiniGL} minigl 18 | * @param {object} properties 19 | */ 20 | constructor(minigl, vertexShaders, fragments, uniforms = {}, properties = {}) { 21 | 22 | // Add additional properties. 23 | Object.assign(this, properties); 24 | 25 | // Set required properties. 26 | this.gl = minigl; 27 | this.uniforms = uniforms; 28 | 29 | const context = this.gl.getContext(); 30 | 31 | const prefix = ` 32 | precision highp float; 33 | `; 34 | 35 | this.vertexSource = ` 36 | ${prefix} 37 | attribute vec4 position; 38 | attribute vec2 uv; 39 | attribute vec2 uvNorm; 40 | ${this._getUniformVariableDeclarations(this.gl.commonUniforms,"vertex")} 41 | ${this._getUniformVariableDeclarations(uniforms,"vertex")} 42 | ${vertexShaders} 43 | `; 44 | 45 | this.Source = ` 46 | ${prefix} 47 | ${this._getUniformVariableDeclarations(this.gl.commonUniforms,"fragment")} 48 | ${this._getUniformVariableDeclarations(uniforms,"fragment")} 49 | ${fragments} 50 | `; 51 | 52 | this.vertexShader = this._getShaderByType(context.VERTEX_SHADER, this.vertexSource); 53 | this.fragmentShader = this._getShaderByType(context.FRAGMENT_SHADER, this.Source); 54 | this.program = context.createProgram(); 55 | 56 | context.attachShader(this.program, this.vertexShader); 57 | context.attachShader(this.program, this.fragmentShader); 58 | context.linkProgram(this.program); 59 | context.getProgramParameter(this.program, context.LINK_STATUS) || console.error(context.getProgramInfoLog(this.program)); 60 | context.useProgram(this.program); 61 | 62 | this.attachUniforms(void 0, this.gl.commonUniforms); 63 | this.attachUniforms(void 0, this.uniforms); 64 | 65 | } 66 | 67 | _getShaderByType(type, source) { 68 | const context = this.gl.getContext(); 69 | const shader = context.createShader(type); 70 | context.shaderSource(shader, source); 71 | context.compileShader(shader); 72 | 73 | if (!context.getShaderParameter(shader, context.COMPILE_STATUS)) { 74 | console.error(context.getShaderInfoLog(shader)); 75 | } 76 | 77 | return shader; 78 | } 79 | 80 | _getUniformVariableDeclarations(uniforms, type) { 81 | return Object.entries(uniforms).map(([uniform, value]) => { 82 | return value.getDeclaration(uniform, type); 83 | }).join("\n"); 84 | } 85 | 86 | attachUniforms(name, uniforms) { 87 | if (!name) { 88 | Object.entries(uniforms).forEach(([name, uniform]) => { 89 | this.attachUniforms(name, uniform); 90 | }); 91 | } else if (uniforms.type === 'array') { 92 | uniforms.value.forEach((uniform, i) => { 93 | this.attachUniforms(`${name}[${i}]`, uniform); 94 | }); 95 | } else if (uniforms.type === 'struct') { 96 | Object.entries(uniforms.value).forEach(([uniform, i]) => { 97 | this.attachUniforms(`${name}.${uniform}`, i); 98 | }); 99 | } else { 100 | this.uniformInstances.push({ 101 | uniform: uniforms, 102 | location: this.gl.getContext().getUniformLocation(this.program, name) 103 | }); 104 | } 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/Mesh.js: -------------------------------------------------------------------------------- 1 | export default class Mesh { 2 | 3 | /** 4 | * The parent MiniGL controller. 5 | * 6 | * @type {MiniGL} 7 | * @private 8 | */ 9 | gl; 10 | 11 | wireframe = false; 12 | attributeInstances = []; 13 | 14 | /** 15 | * @param {MiniGL} minigl 16 | * @param geometry 17 | * @param material 18 | * @param {object} properties 19 | */ 20 | constructor(minigl, geometry, material, properties = {}) { 21 | 22 | // Add additional properties. 23 | Object.assign(this, properties); 24 | 25 | // Set required properties. 26 | this.geometry = geometry; 27 | this.material = material; 28 | this.gl = minigl; 29 | 30 | // Build `attributeInstances` array. 31 | Object.entries(this.geometry.attributes).forEach(([e, attribute]) => { 32 | this.attributeInstances.push({ 33 | attribute: attribute, 34 | location: attribute.attach(e, this.material.program) 35 | }); 36 | }); 37 | 38 | // Add mesh to MiniGL controller. 39 | this.gl.meshes.push(this); 40 | } 41 | 42 | 43 | draw() { 44 | const context = this.gl.getContext(); 45 | 46 | context.useProgram(this.material.program); 47 | 48 | this.material.uniformInstances.forEach(({uniform: uniform, location: location}) => { 49 | uniform.update(location); 50 | }); 51 | 52 | this.attributeInstances.forEach(({attribute: attribute, location: location}) => { 53 | attribute.use(location); 54 | }); 55 | 56 | const mode = this.wireframe ? context.LINES : context.TRIANGLES; 57 | 58 | context.drawElements(mode, this.geometry.attributes.index.values.length, context.UNSIGNED_SHORT, 0); 59 | } 60 | 61 | remove() { 62 | this.gl.meshes = this.gl.meshes.filter(mesh => mesh != this); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/MiniGL.js: -------------------------------------------------------------------------------- 1 | import Attribute from './Attribute.js'; 2 | import Material from './Material.js'; 3 | import Mesh from './Mesh.js'; 4 | import PlaneGeometry from './PlaneGeometry.js'; 5 | import Uniform from './Uniform.js'; 6 | 7 | export default class MiniGL { 8 | 9 | /** 10 | * Class reference. 11 | * 12 | * @type {MiniGL} 13 | */ 14 | _class = MiniGL; 15 | 16 | /** 17 | * @type {HTMLCanvasElement} 18 | * @private 19 | */ 20 | _canvas; 21 | 22 | /** 23 | * @type {WebGLRenderingContext} 24 | * @private 25 | */ 26 | _context; 27 | 28 | /** 29 | * @type {object} 30 | */ 31 | commonUniforms = {}; 32 | 33 | /** 34 | * @type {array} 35 | */ 36 | meshes = []; 37 | 38 | /** 39 | * @param {HTMLCanvasElement} canvas 40 | * @param {null|Number} width 41 | * @param {null|Number} height 42 | */ 43 | constructor(canvas, width, height) { 44 | this.setCanvas(canvas); 45 | 46 | const matrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; 47 | 48 | this.commonUniforms = { 49 | projectionMatrix: new Uniform(this, 'mat4', matrix), 50 | modelViewMatrix: new Uniform(this, 'mat4', matrix), 51 | resolution: new Uniform(this, 'vec2', [1, 1]), 52 | aspectRatio: new Uniform(this, 'float', 1) 53 | }; 54 | 55 | this.setSize(width, height); 56 | } 57 | 58 | /** 59 | * Sets the `_canvas` and `_context` properties. 60 | * 61 | * @param {HTMLCanvasElement} canvas 62 | */ 63 | setCanvas(canvas) { 64 | this._canvas = canvas; 65 | this._context = canvas.getContext('webgl', { 66 | antialias: true 67 | }); 68 | } 69 | 70 | /** 71 | * @return {HTMLCanvasElement} 72 | */ 73 | getCanvas() { 74 | return this._canvas; 75 | } 76 | 77 | /** 78 | * @return {WebGLRenderingContext} 79 | */ 80 | getContext() { 81 | return this._context; 82 | } 83 | 84 | /** 85 | * Set the canvas and viewport size. 86 | * 87 | * @param {Number} width 88 | * @param {Number} height 89 | */ 90 | setSize(width = 640, height = 480) { 91 | this.getCanvas().width = width; 92 | this.getCanvas().height = height; 93 | this.getContext().viewport(0, 0, width, height); 94 | this.commonUniforms.resolution.value = [width, height]; 95 | this.commonUniforms.aspectRatio.value = width / height; 96 | } 97 | 98 | setOrthographicCamera(left = 0, right = 0, top = 0, bottom = -2000, distance = 2000) { 99 | this.commonUniforms.projectionMatrix.value = [ 100 | 2 / this.getCanvas().width, 101 | 0, 0, 0, 0, 102 | 2 / this.getCanvas().height, 103 | 0, 0, 0, 0, 104 | 2 / (bottom - distance), 105 | 0, left, right, top, 1 106 | ]; 107 | } 108 | 109 | render() { 110 | this.getContext().clearColor(0, 0, 0, 0); 111 | this.getContext().clearDepth(1); 112 | this.meshes.forEach(mesh => { 113 | mesh.draw(); 114 | }); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/PlaneGeometry.js: -------------------------------------------------------------------------------- 1 | import Attribute from './Attribute.js'; 2 | 3 | export default class PlaneGeometry { 4 | 5 | /** 6 | * The parent MiniGL controller. 7 | * 8 | * @type {MiniGL} 9 | * @private 10 | */ 11 | gl; 12 | 13 | attributes; 14 | 15 | /** 16 | * 17 | * @param {MiniGL} minigl 18 | * @param width 19 | * @param height 20 | * @param n 21 | * @param i 22 | * @param orientation 23 | * @param {object} properties 24 | */ 25 | constructor(minigl, width, height, n, i, orientation, properties = {}) { 26 | 27 | // Add additional properties. 28 | Object.assign(this, properties); 29 | 30 | // Set required properties. 31 | this.gl = minigl; 32 | 33 | const context = this.gl.getContext(); 34 | 35 | context.createBuffer(); 36 | 37 | this.attributes = { 38 | position: new Attribute(this.gl, { 39 | target: context.ARRAY_BUFFER, 40 | size: 3 41 | }), 42 | uv: new Attribute(this.gl, { 43 | target: context.ARRAY_BUFFER, 44 | size: 2 45 | }), 46 | uvNorm: new Attribute(this.gl, { 47 | target: context.ARRAY_BUFFER, 48 | size: 2 49 | }), 50 | index: new Attribute(this.gl, { 51 | target: context.ELEMENT_ARRAY_BUFFER, 52 | size: 3, 53 | type: context.UNSIGNED_SHORT 54 | }) 55 | }; 56 | 57 | this.setTopology(n, i); 58 | this.setSize(width, height, orientation); 59 | } 60 | 61 | setTopology(e = 1, t = 1) { 62 | this.xSegCount = e; 63 | this.ySegCount = t; 64 | this.vertexCount = (this.xSegCount + 1) * (this.ySegCount + 1); 65 | this.quadCount = this.xSegCount * this.ySegCount * 2; 66 | this.attributes.uv.values = new Float32Array(2 * this.vertexCount); 67 | this.attributes.uvNorm.values = new Float32Array(2 * this.vertexCount); 68 | this.attributes.index.values = new Uint16Array(3 * this.quadCount); 69 | 70 | for (let e = 0; e <= this.ySegCount; e++) { 71 | for (let t = 0; t <= this.xSegCount; t++) { 72 | const i = e * (this.xSegCount + 1) + t; 73 | if (this.attributes.uv.values[2 * i] = t / this.xSegCount, this.attributes.uv.values[2 * i + 1] = 1 - e / this.ySegCount, this.attributes.uvNorm.values[2 * i] = t / this.xSegCount * 2 - 1, this.attributes.uvNorm.values[2 * i + 1] = 1 - e / this.ySegCount * 2, t < this.xSegCount && e < this.ySegCount) { 74 | const s = e * this.xSegCount + t; 75 | this.attributes.index.values[6 * s] = i, this.attributes.index.values[6 * s + 1] = i + 1 + this.xSegCount, this.attributes.index.values[6 * s + 2] = i + 1, this.attributes.index.values[6 * s + 3] = i + 1, this.attributes.index.values[6 * s + 4] = i + 1 + this.xSegCount, this.attributes.index.values[6 * s + 5] = i + 2 + this.xSegCount 76 | } 77 | } 78 | } 79 | 80 | this.attributes.uv.update(); 81 | this.attributes.uvNorm.update(); 82 | this.attributes.index.update(); 83 | } 84 | 85 | setSize(width = 1, height = 1, orientation = 'xz') { 86 | this.width = width; 87 | this.height = height; 88 | this.orientation = orientation; 89 | 90 | this.attributes.position.values && this.attributes.position.values.length === 3 * this.vertexCount || (this.attributes.position.values = new Float32Array(3 * this.vertexCount)); 91 | const o = width / -2; 92 | const r = height / -2; 93 | const segment_width = width / this.xSegCount; 94 | const segment_height = height / this.ySegCount; 95 | 96 | for (let yIndex = 0; yIndex <= this.ySegCount; yIndex++) { 97 | const t = r + yIndex * segment_height; 98 | for (let xIndex = 0; xIndex <= this.xSegCount; xIndex++) { 99 | const r = o + xIndex * segment_width; 100 | const l = yIndex * (this.xSegCount + 1) + xIndex; 101 | 102 | this.attributes.position.values[3 * l + 'xyz'.indexOf(orientation[0])] = r; 103 | this.attributes.position.values[3 * l + 'xyz'.indexOf(orientation[1])] = -t; 104 | } 105 | } 106 | 107 | this.attributes.position.update(); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/Shaders/Blend.glsl: -------------------------------------------------------------------------------- 1 | // 2 | // https://github.com/jamieowen/glsl-blend 3 | // 4 | 5 | // Normal 6 | 7 | vec3 blendNormal(vec3 base, vec3 blend) { 8 | return blend; 9 | } 10 | 11 | vec3 blendNormal(vec3 base, vec3 blend, float opacity) { 12 | return (blendNormal(base, blend) * opacity + base * (1.0 - opacity)); 13 | } 14 | 15 | // Screen 16 | 17 | float blendScreen(float base, float blend) { 18 | return 1.0-((1.0-base)*(1.0-blend)); 19 | } 20 | 21 | vec3 blendScreen(vec3 base, vec3 blend) { 22 | return vec3(blendScreen(base.r,blend.r),blendScreen(base.g,blend.g),blendScreen(base.b,blend.b)); 23 | } 24 | 25 | vec3 blendScreen(vec3 base, vec3 blend, float opacity) { 26 | return (blendScreen(base, blend) * opacity + base * (1.0 - opacity)); 27 | } 28 | 29 | // Multiply 30 | 31 | vec3 blendMultiply(vec3 base, vec3 blend) { 32 | return base*blend; 33 | } 34 | 35 | vec3 blendMultiply(vec3 base, vec3 blend, float opacity) { 36 | return (blendMultiply(base, blend) * opacity + base * (1.0 - opacity)); 37 | } 38 | 39 | // Overlay 40 | 41 | float blendOverlay(float base, float blend) { 42 | return base<0.5?(2.0*base*blend):(1.0-2.0*(1.0-base)*(1.0-blend)); 43 | } 44 | 45 | vec3 blendOverlay(vec3 base, vec3 blend) { 46 | return vec3(blendOverlay(base.r,blend.r),blendOverlay(base.g,blend.g),blendOverlay(base.b,blend.b)); 47 | } 48 | 49 | vec3 blendOverlay(vec3 base, vec3 blend, float opacity) { 50 | return (blendOverlay(base, blend) * opacity + base * (1.0 - opacity)); 51 | } 52 | 53 | // Hard light 54 | 55 | vec3 blendHardLight(vec3 base, vec3 blend) { 56 | return blendOverlay(blend,base); 57 | } 58 | 59 | vec3 blendHardLight(vec3 base, vec3 blend, float opacity) { 60 | return (blendHardLight(base, blend) * opacity + base * (1.0 - opacity)); 61 | } 62 | 63 | // Soft light 64 | 65 | float blendSoftLight(float base, float blend) { 66 | return (blend<0.5)?(2.0*base*blend+base*base*(1.0-2.0*blend)):(sqrt(base)*(2.0*blend-1.0)+2.0*base*(1.0-blend)); 67 | } 68 | 69 | vec3 blendSoftLight(vec3 base, vec3 blend) { 70 | return vec3(blendSoftLight(base.r,blend.r),blendSoftLight(base.g,blend.g),blendSoftLight(base.b,blend.b)); 71 | } 72 | 73 | vec3 blendSoftLight(vec3 base, vec3 blend, float opacity) { 74 | return (blendSoftLight(base, blend) * opacity + base * (1.0 - opacity)); 75 | } 76 | 77 | // Color dodge 78 | 79 | float blendColorDodge(float base, float blend) { 80 | return (blend==1.0)?blend:min(base/(1.0-blend),1.0); 81 | } 82 | 83 | vec3 blendColorDodge(vec3 base, vec3 blend) { 84 | return vec3(blendColorDodge(base.r,blend.r),blendColorDodge(base.g,blend.g),blendColorDodge(base.b,blend.b)); 85 | } 86 | 87 | vec3 blendColorDodge(vec3 base, vec3 blend, float opacity) { 88 | return (blendColorDodge(base, blend) * opacity + base * (1.0 - opacity)); 89 | } 90 | 91 | // Color burn 92 | 93 | float blendColorBurn(float base, float blend) { 94 | return (blend==0.0)?blend:max((1.0-((1.0-base)/blend)),0.0); 95 | } 96 | 97 | vec3 blendColorBurn(vec3 base, vec3 blend) { 98 | return vec3(blendColorBurn(base.r,blend.r),blendColorBurn(base.g,blend.g),blendColorBurn(base.b,blend.b)); 99 | } 100 | 101 | vec3 blendColorBurn(vec3 base, vec3 blend, float opacity) { 102 | return (blendColorBurn(base, blend) * opacity + base * (1.0 - opacity)); 103 | } 104 | 105 | // Vivid Light 106 | 107 | float blendVividLight(float base, float blend) { 108 | return (blend<0.5)?blendColorBurn(base,(2.0*blend)):blendColorDodge(base,(2.0*(blend-0.5))); 109 | } 110 | 111 | vec3 blendVividLight(vec3 base, vec3 blend) { 112 | return vec3(blendVividLight(base.r,blend.r),blendVividLight(base.g,blend.g),blendVividLight(base.b,blend.b)); 113 | } 114 | 115 | vec3 blendVividLight(vec3 base, vec3 blend, float opacity) { 116 | return (blendVividLight(base, blend) * opacity + base * (1.0 - opacity)); 117 | } 118 | 119 | // Lighten 120 | 121 | float blendLighten(float base, float blend) { 122 | return max(blend,base); 123 | } 124 | 125 | vec3 blendLighten(vec3 base, vec3 blend) { 126 | return vec3(blendLighten(base.r,blend.r),blendLighten(base.g,blend.g),blendLighten(base.b,blend.b)); 127 | } 128 | 129 | vec3 blendLighten(vec3 base, vec3 blend, float opacity) { 130 | return (blendLighten(base, blend) * opacity + base * (1.0 - opacity)); 131 | } 132 | 133 | // Linear burn 134 | 135 | float blendLinearBurn(float base, float blend) { 136 | // Note : Same implementation as BlendSubtractf 137 | return max(base+blend-1.0,0.0); 138 | } 139 | 140 | vec3 blendLinearBurn(vec3 base, vec3 blend) { 141 | // Note : Same implementation as BlendSubtract 142 | return max(base+blend-vec3(1.0),vec3(0.0)); 143 | } 144 | 145 | vec3 blendLinearBurn(vec3 base, vec3 blend, float opacity) { 146 | return (blendLinearBurn(base, blend) * opacity + base * (1.0 - opacity)); 147 | } 148 | 149 | // Linear dodge 150 | 151 | float blendLinearDodge(float base, float blend) { 152 | // Note : Same implementation as BlendAddf 153 | return min(base+blend,1.0); 154 | } 155 | 156 | vec3 blendLinearDodge(vec3 base, vec3 blend) { 157 | // Note : Same implementation as BlendAdd 158 | return min(base+blend,vec3(1.0)); 159 | } 160 | 161 | vec3 blendLinearDodge(vec3 base, vec3 blend, float opacity) { 162 | return (blendLinearDodge(base, blend) * opacity + base * (1.0 - opacity)); 163 | } 164 | 165 | // Linear light 166 | 167 | float blendLinearLight(float base, float blend) { 168 | return blend<0.5?blendLinearBurn(base,(2.0*blend)):blendLinearDodge(base,(2.0*(blend-0.5))); 169 | } 170 | 171 | vec3 blendLinearLight(vec3 base, vec3 blend) { 172 | return vec3(blendLinearLight(base.r,blend.r),blendLinearLight(base.g,blend.g),blendLinearLight(base.b,blend.b)); 173 | } 174 | 175 | vec3 blendLinearLight(vec3 base, vec3 blend, float opacity) { 176 | return (blendLinearLight(base, blend) * opacity + base * (1.0 - opacity)); 177 | } 178 | -------------------------------------------------------------------------------- /src/Shaders/Fragment.glsl: -------------------------------------------------------------------------------- 1 | varying vec3 v_color; 2 | 3 | void main() { 4 | vec3 color = v_color; 5 | if (u_darken_top == 1.0) { 6 | vec2 st = gl_FragCoord.xy/resolution.xy; 7 | color.g -= pow(st.y + sin(-12.0) * st.x, u_shadow_power) * 0.4; 8 | } 9 | gl_FragColor = vec4(color, 1.0); 10 | } 11 | -------------------------------------------------------------------------------- /src/Shaders/Noise.glsl: -------------------------------------------------------------------------------- 1 | // 2 | // Description : Array and textureless GLSL 2D/3D/4D simplex 3 | // noise functions. 4 | // Author : Ian McEwan, Ashima Arts. 5 | // Maintainer : stegu 6 | // Lastmod : 20110822 (ijm) 7 | // License : Copyright (C) 2011 Ashima Arts. All rights reserved. 8 | // Distributed under the MIT License. See LICENSE file. 9 | // https://github.com/ashima/webgl-noise 10 | // https://github.com/stegu/webgl-noise 11 | // 12 | 13 | vec3 mod289(vec3 x) { 14 | return x - floor(x * (1.0 / 289.0)) * 289.0; 15 | } 16 | 17 | vec4 mod289(vec4 x) { 18 | return x - floor(x * (1.0 / 289.0)) * 289.0; 19 | } 20 | 21 | vec4 permute(vec4 x) { 22 | return mod289(((x*34.0)+1.0)*x); 23 | } 24 | 25 | vec4 taylorInvSqrt(vec4 r) 26 | { 27 | return 1.79284291400159 - 0.85373472095314 * r; 28 | } 29 | 30 | float snoise(vec3 v) 31 | { 32 | const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; 33 | const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); 34 | 35 | // First corner 36 | vec3 i = floor(v + dot(v, C.yyy) ); 37 | vec3 x0 = v - i + dot(i, C.xxx) ; 38 | 39 | // Other corners 40 | vec3 g = step(x0.yzx, x0.xyz); 41 | vec3 l = 1.0 - g; 42 | vec3 i1 = min( g.xyz, l.zxy ); 43 | vec3 i2 = max( g.xyz, l.zxy ); 44 | 45 | // x0 = x0 - 0.0 + 0.0 * C.xxx; 46 | // x1 = x0 - i1 + 1.0 * C.xxx; 47 | // x2 = x0 - i2 + 2.0 * C.xxx; 48 | // x3 = x0 - 1.0 + 3.0 * C.xxx; 49 | vec3 x1 = x0 - i1 + C.xxx; 50 | vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y 51 | vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y 52 | 53 | // Permutations 54 | i = mod289(i); 55 | vec4 p = permute( permute( permute( 56 | i.z + vec4(0.0, i1.z, i2.z, 1.0 )) 57 | + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 58 | + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); 59 | 60 | // Gradients: 7x7 points over a square, mapped onto an octahedron. 61 | // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) 62 | float n_ = 0.142857142857; // 1.0/7.0 63 | vec3 ns = n_ * D.wyz - D.xzx; 64 | 65 | vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7) 66 | 67 | vec4 x_ = floor(j * ns.z); 68 | vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) 69 | 70 | vec4 x = x_ *ns.x + ns.yyyy; 71 | vec4 y = y_ *ns.x + ns.yyyy; 72 | vec4 h = 1.0 - abs(x) - abs(y); 73 | 74 | vec4 b0 = vec4( x.xy, y.xy ); 75 | vec4 b1 = vec4( x.zw, y.zw ); 76 | 77 | //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0; 78 | //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0; 79 | vec4 s0 = floor(b0)*2.0 + 1.0; 80 | vec4 s1 = floor(b1)*2.0 + 1.0; 81 | vec4 sh = -step(h, vec4(0.0)); 82 | 83 | vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; 84 | vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; 85 | 86 | vec3 p0 = vec3(a0.xy,h.x); 87 | vec3 p1 = vec3(a0.zw,h.y); 88 | vec3 p2 = vec3(a1.xy,h.z); 89 | vec3 p3 = vec3(a1.zw,h.w); 90 | 91 | //Normalise gradients 92 | vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); 93 | p0 *= norm.x; 94 | p1 *= norm.y; 95 | p2 *= norm.z; 96 | p3 *= norm.w; 97 | 98 | // Mix final noise value 99 | vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); 100 | m = m * m; 101 | return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 102 | dot(p2,x2), dot(p3,x3) ) ); 103 | } 104 | -------------------------------------------------------------------------------- /src/Shaders/Vertex.glsl: -------------------------------------------------------------------------------- 1 | varying vec3 v_color; 2 | 3 | void main() { 4 | float time = u_time * u_global.noiseSpeed; 5 | 6 | vec2 noiseCoord = resolution * uvNorm * u_global.noiseFreq; 7 | 8 | vec2 st = 1. - uvNorm.xy; 9 | 10 | // 11 | // Tilting the plane 12 | // 13 | 14 | // Front-to-back tilt 15 | float tilt = resolution.y / 2.0 * uvNorm.y; 16 | 17 | // Left-to-right angle 18 | float incline = resolution.x * uvNorm.x / 2.0 * u_vertDeform.incline; 19 | 20 | // Up-down shift to offset incline 21 | float offset = resolution.x / 2.0 * u_vertDeform.incline * mix(u_vertDeform.offsetBottom, u_vertDeform.offsetTop, uv.y); 22 | 23 | // 24 | // Vertex noise 25 | // 26 | 27 | float noise = snoise(vec3( 28 | noiseCoord.x * u_vertDeform.noiseFreq.x + time * u_vertDeform.noiseFlow, 29 | noiseCoord.y * u_vertDeform.noiseFreq.y, 30 | time * u_vertDeform.noiseSpeed + u_vertDeform.noiseSeed 31 | )) * u_vertDeform.noiseAmp; 32 | 33 | // Fade noise to zero at edges 34 | noise *= 1.0 - pow(abs(uvNorm.y), 2.0); 35 | 36 | // Clamp to 0 37 | noise = max(0.0, noise); 38 | 39 | vec3 pos = vec3( 40 | position.x, 41 | position.y + tilt + incline + noise - offset, 42 | position.z 43 | ); 44 | 45 | // 46 | // Vertex color, to be passed to fragment shader 47 | // 48 | 49 | if (u_active_colors[0] == 1.) { 50 | v_color = u_baseColor; 51 | } 52 | 53 | for (int i = 0; i < u_waveLayers_length; i++) { 54 | if (u_active_colors[i] == 1.) { 55 | WaveLayers layer = u_waveLayers[i]; 56 | 57 | float noise = smoothstep( 58 | layer.noiseFloor, 59 | layer.noiseCeil, 60 | snoise(vec3( 61 | noiseCoord.x * layer.noiseFreq.x + time * layer.noiseFlow, 62 | noiseCoord.y * layer.noiseFreq.y, 63 | time * layer.noiseSpeed + layer.noiseSeed 64 | )) / 2.0 + 0.5 65 | ); 66 | 67 | v_color = blendNormal(v_color, layer.color, pow(noise, 4.)); 68 | } 69 | } 70 | 71 | // 72 | // Finish 73 | // 74 | 75 | gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0); 76 | } 77 | -------------------------------------------------------------------------------- /src/ShadersJs/Blend.js: -------------------------------------------------------------------------------- 1 | export default `// 2 | // https://github.com/jamieowen/glsl-blend 3 | // 4 | 5 | // Normal 6 | 7 | vec3 blendNormal(vec3 base, vec3 blend) { 8 | return blend; 9 | } 10 | 11 | vec3 blendNormal(vec3 base, vec3 blend, float opacity) { 12 | return (blendNormal(base, blend) * opacity + base * (1.0 - opacity)); 13 | } 14 | 15 | // Screen 16 | 17 | float blendScreen(float base, float blend) { 18 | return 1.0-((1.0-base)*(1.0-blend)); 19 | } 20 | 21 | vec3 blendScreen(vec3 base, vec3 blend) { 22 | return vec3(blendScreen(base.r,blend.r),blendScreen(base.g,blend.g),blendScreen(base.b,blend.b)); 23 | } 24 | 25 | vec3 blendScreen(vec3 base, vec3 blend, float opacity) { 26 | return (blendScreen(base, blend) * opacity + base * (1.0 - opacity)); 27 | } 28 | 29 | // Multiply 30 | 31 | vec3 blendMultiply(vec3 base, vec3 blend) { 32 | return base*blend; 33 | } 34 | 35 | vec3 blendMultiply(vec3 base, vec3 blend, float opacity) { 36 | return (blendMultiply(base, blend) * opacity + base * (1.0 - opacity)); 37 | } 38 | 39 | // Overlay 40 | 41 | float blendOverlay(float base, float blend) { 42 | return base<0.5?(2.0*base*blend):(1.0-2.0*(1.0-base)*(1.0-blend)); 43 | } 44 | 45 | vec3 blendOverlay(vec3 base, vec3 blend) { 46 | return vec3(blendOverlay(base.r,blend.r),blendOverlay(base.g,blend.g),blendOverlay(base.b,blend.b)); 47 | } 48 | 49 | vec3 blendOverlay(vec3 base, vec3 blend, float opacity) { 50 | return (blendOverlay(base, blend) * opacity + base * (1.0 - opacity)); 51 | } 52 | 53 | // Hard light 54 | 55 | vec3 blendHardLight(vec3 base, vec3 blend) { 56 | return blendOverlay(blend,base); 57 | } 58 | 59 | vec3 blendHardLight(vec3 base, vec3 blend, float opacity) { 60 | return (blendHardLight(base, blend) * opacity + base * (1.0 - opacity)); 61 | } 62 | 63 | // Soft light 64 | 65 | float blendSoftLight(float base, float blend) { 66 | return (blend<0.5)?(2.0*base*blend+base*base*(1.0-2.0*blend)):(sqrt(base)*(2.0*blend-1.0)+2.0*base*(1.0-blend)); 67 | } 68 | 69 | vec3 blendSoftLight(vec3 base, vec3 blend) { 70 | return vec3(blendSoftLight(base.r,blend.r),blendSoftLight(base.g,blend.g),blendSoftLight(base.b,blend.b)); 71 | } 72 | 73 | vec3 blendSoftLight(vec3 base, vec3 blend, float opacity) { 74 | return (blendSoftLight(base, blend) * opacity + base * (1.0 - opacity)); 75 | } 76 | 77 | // Color dodge 78 | 79 | float blendColorDodge(float base, float blend) { 80 | return (blend==1.0)?blend:min(base/(1.0-blend),1.0); 81 | } 82 | 83 | vec3 blendColorDodge(vec3 base, vec3 blend) { 84 | return vec3(blendColorDodge(base.r,blend.r),blendColorDodge(base.g,blend.g),blendColorDodge(base.b,blend.b)); 85 | } 86 | 87 | vec3 blendColorDodge(vec3 base, vec3 blend, float opacity) { 88 | return (blendColorDodge(base, blend) * opacity + base * (1.0 - opacity)); 89 | } 90 | 91 | // Color burn 92 | 93 | float blendColorBurn(float base, float blend) { 94 | return (blend==0.0)?blend:max((1.0-((1.0-base)/blend)),0.0); 95 | } 96 | 97 | vec3 blendColorBurn(vec3 base, vec3 blend) { 98 | return vec3(blendColorBurn(base.r,blend.r),blendColorBurn(base.g,blend.g),blendColorBurn(base.b,blend.b)); 99 | } 100 | 101 | vec3 blendColorBurn(vec3 base, vec3 blend, float opacity) { 102 | return (blendColorBurn(base, blend) * opacity + base * (1.0 - opacity)); 103 | } 104 | 105 | // Vivid Light 106 | 107 | float blendVividLight(float base, float blend) { 108 | return (blend<0.5)?blendColorBurn(base,(2.0*blend)):blendColorDodge(base,(2.0*(blend-0.5))); 109 | } 110 | 111 | vec3 blendVividLight(vec3 base, vec3 blend) { 112 | return vec3(blendVividLight(base.r,blend.r),blendVividLight(base.g,blend.g),blendVividLight(base.b,blend.b)); 113 | } 114 | 115 | vec3 blendVividLight(vec3 base, vec3 blend, float opacity) { 116 | return (blendVividLight(base, blend) * opacity + base * (1.0 - opacity)); 117 | } 118 | 119 | // Lighten 120 | 121 | float blendLighten(float base, float blend) { 122 | return max(blend,base); 123 | } 124 | 125 | vec3 blendLighten(vec3 base, vec3 blend) { 126 | return vec3(blendLighten(base.r,blend.r),blendLighten(base.g,blend.g),blendLighten(base.b,blend.b)); 127 | } 128 | 129 | vec3 blendLighten(vec3 base, vec3 blend, float opacity) { 130 | return (blendLighten(base, blend) * opacity + base * (1.0 - opacity)); 131 | } 132 | 133 | // Linear burn 134 | 135 | float blendLinearBurn(float base, float blend) { 136 | // Note : Same implementation as BlendSubtractf 137 | return max(base+blend-1.0,0.0); 138 | } 139 | 140 | vec3 blendLinearBurn(vec3 base, vec3 blend) { 141 | // Note : Same implementation as BlendSubtract 142 | return max(base+blend-vec3(1.0),vec3(0.0)); 143 | } 144 | 145 | vec3 blendLinearBurn(vec3 base, vec3 blend, float opacity) { 146 | return (blendLinearBurn(base, blend) * opacity + base * (1.0 - opacity)); 147 | } 148 | 149 | // Linear dodge 150 | 151 | float blendLinearDodge(float base, float blend) { 152 | // Note : Same implementation as BlendAddf 153 | return min(base+blend,1.0); 154 | } 155 | 156 | vec3 blendLinearDodge(vec3 base, vec3 blend) { 157 | // Note : Same implementation as BlendAdd 158 | return min(base+blend,vec3(1.0)); 159 | } 160 | 161 | vec3 blendLinearDodge(vec3 base, vec3 blend, float opacity) { 162 | return (blendLinearDodge(base, blend) * opacity + base * (1.0 - opacity)); 163 | } 164 | 165 | // Linear light 166 | 167 | float blendLinearLight(float base, float blend) { 168 | return blend<0.5?blendLinearBurn(base,(2.0*blend)):blendLinearDodge(base,(2.0*(blend-0.5))); 169 | } 170 | 171 | vec3 blendLinearLight(vec3 base, vec3 blend) { 172 | return vec3(blendLinearLight(base.r,blend.r),blendLinearLight(base.g,blend.g),blendLinearLight(base.b,blend.b)); 173 | } 174 | 175 | vec3 blendLinearLight(vec3 base, vec3 blend, float opacity) { 176 | return (blendLinearLight(base, blend) * opacity + base * (1.0 - opacity)); 177 | } 178 | `; 179 | -------------------------------------------------------------------------------- /src/ShadersJs/Fragment.js: -------------------------------------------------------------------------------- 1 | export default `varying vec3 v_color; 2 | 3 | void main() { 4 | vec3 color = v_color; 5 | if (u_darken_top == 1.0) { 6 | vec2 st = gl_FragCoord.xy/resolution.xy; 7 | color.g -= pow(st.y + sin(-12.0) * st.x, u_shadow_power) * 0.4; 8 | } 9 | gl_FragColor = vec4(color, 1.0); 10 | } 11 | `; 12 | -------------------------------------------------------------------------------- /src/ShadersJs/Noise.js: -------------------------------------------------------------------------------- 1 | export default `// 2 | // Description : Array and textureless GLSL 2D/3D/4D simplex 3 | // noise functions. 4 | // Author : Ian McEwan, Ashima Arts. 5 | // Maintainer : stegu 6 | // Lastmod : 20110822 (ijm) 7 | // License : Copyright (C) 2011 Ashima Arts. All rights reserved. 8 | // Distributed under the MIT License. See LICENSE file. 9 | // https://github.com/ashima/webgl-noise 10 | // https://github.com/stegu/webgl-noise 11 | // 12 | 13 | vec3 mod289(vec3 x) { 14 | return x - floor(x * (1.0 / 289.0)) * 289.0; 15 | } 16 | 17 | vec4 mod289(vec4 x) { 18 | return x - floor(x * (1.0 / 289.0)) * 289.0; 19 | } 20 | 21 | vec4 permute(vec4 x) { 22 | return mod289(((x*34.0)+1.0)*x); 23 | } 24 | 25 | vec4 taylorInvSqrt(vec4 r) 26 | { 27 | return 1.79284291400159 - 0.85373472095314 * r; 28 | } 29 | 30 | float snoise(vec3 v) 31 | { 32 | const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; 33 | const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); 34 | 35 | // First corner 36 | vec3 i = floor(v + dot(v, C.yyy) ); 37 | vec3 x0 = v - i + dot(i, C.xxx) ; 38 | 39 | // Other corners 40 | vec3 g = step(x0.yzx, x0.xyz); 41 | vec3 l = 1.0 - g; 42 | vec3 i1 = min( g.xyz, l.zxy ); 43 | vec3 i2 = max( g.xyz, l.zxy ); 44 | 45 | // x0 = x0 - 0.0 + 0.0 * C.xxx; 46 | // x1 = x0 - i1 + 1.0 * C.xxx; 47 | // x2 = x0 - i2 + 2.0 * C.xxx; 48 | // x3 = x0 - 1.0 + 3.0 * C.xxx; 49 | vec3 x1 = x0 - i1 + C.xxx; 50 | vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y 51 | vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y 52 | 53 | // Permutations 54 | i = mod289(i); 55 | vec4 p = permute( permute( permute( 56 | i.z + vec4(0.0, i1.z, i2.z, 1.0 )) 57 | + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 58 | + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); 59 | 60 | // Gradients: 7x7 points over a square, mapped onto an octahedron. 61 | // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) 62 | float n_ = 0.142857142857; // 1.0/7.0 63 | vec3 ns = n_ * D.wyz - D.xzx; 64 | 65 | vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7) 66 | 67 | vec4 x_ = floor(j * ns.z); 68 | vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) 69 | 70 | vec4 x = x_ *ns.x + ns.yyyy; 71 | vec4 y = y_ *ns.x + ns.yyyy; 72 | vec4 h = 1.0 - abs(x) - abs(y); 73 | 74 | vec4 b0 = vec4( x.xy, y.xy ); 75 | vec4 b1 = vec4( x.zw, y.zw ); 76 | 77 | //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0; 78 | //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0; 79 | vec4 s0 = floor(b0)*2.0 + 1.0; 80 | vec4 s1 = floor(b1)*2.0 + 1.0; 81 | vec4 sh = -step(h, vec4(0.0)); 82 | 83 | vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; 84 | vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; 85 | 86 | vec3 p0 = vec3(a0.xy,h.x); 87 | vec3 p1 = vec3(a0.zw,h.y); 88 | vec3 p2 = vec3(a1.xy,h.z); 89 | vec3 p3 = vec3(a1.zw,h.w); 90 | 91 | //Normalise gradients 92 | vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); 93 | p0 *= norm.x; 94 | p1 *= norm.y; 95 | p2 *= norm.z; 96 | p3 *= norm.w; 97 | 98 | // Mix final noise value 99 | vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); 100 | m = m * m; 101 | return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 102 | dot(p2,x2), dot(p3,x3) ) ); 103 | } 104 | `; 105 | -------------------------------------------------------------------------------- /src/ShadersJs/Vertex.js: -------------------------------------------------------------------------------- 1 | export default `varying vec3 v_color; 2 | 3 | void main() { 4 | float time = u_time * u_global.noiseSpeed; 5 | 6 | vec2 noiseCoord = resolution * uvNorm * u_global.noiseFreq; 7 | 8 | vec2 st = 1. - uvNorm.xy; 9 | 10 | // 11 | // Tilting the plane 12 | // 13 | 14 | // Front-to-back tilt 15 | float tilt = resolution.y / 2.0 * uvNorm.y; 16 | 17 | // Left-to-right angle 18 | float incline = resolution.x * uvNorm.x / 2.0 * u_vertDeform.incline; 19 | 20 | // Up-down shift to offset incline 21 | float offset = resolution.x / 2.0 * u_vertDeform.incline * mix(u_vertDeform.offsetBottom, u_vertDeform.offsetTop, uv.y); 22 | 23 | // 24 | // Vertex noise 25 | // 26 | 27 | float noise = snoise(vec3( 28 | noiseCoord.x * u_vertDeform.noiseFreq.x + time * u_vertDeform.noiseFlow, 29 | noiseCoord.y * u_vertDeform.noiseFreq.y, 30 | time * u_vertDeform.noiseSpeed + u_vertDeform.noiseSeed 31 | )) * u_vertDeform.noiseAmp; 32 | 33 | // Fade noise to zero at edges 34 | noise *= 1.0 - pow(abs(uvNorm.y), 2.0); 35 | 36 | // Clamp to 0 37 | noise = max(0.0, noise); 38 | 39 | vec3 pos = vec3( 40 | position.x, 41 | position.y + tilt + incline + noise - offset, 42 | position.z 43 | ); 44 | 45 | // 46 | // Vertex color, to be passed to fragment shader 47 | // 48 | 49 | if (u_active_colors[0] == 1.) { 50 | v_color = u_baseColor; 51 | } 52 | 53 | for (int i = 0; i < u_waveLayers_length; i++) { 54 | if (u_active_colors[i + 1] == 1.) { 55 | WaveLayers layer = u_waveLayers[i]; 56 | 57 | float noise = smoothstep( 58 | layer.noiseFloor, 59 | layer.noiseCeil, 60 | snoise(vec3( 61 | noiseCoord.x * layer.noiseFreq.x + time * layer.noiseFlow, 62 | noiseCoord.y * layer.noiseFreq.y, 63 | time * layer.noiseSpeed + layer.noiseSeed 64 | )) / 2.0 + 0.5 65 | ); 66 | 67 | v_color = blendNormal(v_color, layer.color, pow(noise, 4.)); 68 | } 69 | } 70 | 71 | // 72 | // Finish 73 | // 74 | 75 | gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0); 76 | } 77 | `; 78 | -------------------------------------------------------------------------------- /src/Uniform.js: -------------------------------------------------------------------------------- 1 | export default class Uniform { 2 | 3 | /** 4 | * The parent MiniGL controller. 5 | * 6 | * @type {MiniGL} 7 | * @private 8 | */ 9 | gl; 10 | 11 | /** 12 | * @type {string} 13 | */ 14 | type; 15 | 16 | /** 17 | * @type {*} 18 | */ 19 | value; 20 | 21 | /** 22 | * The mapped type function. 23 | * 24 | * @type {string} 25 | */ 26 | typeFn; 27 | 28 | /** 29 | * Type function mappings. 30 | * 31 | * @type {object} 32 | * @private 33 | */ 34 | _typeMap = { 35 | float: '1f', 36 | int: '1i', 37 | vec2: '2fv', 38 | vec3: '3fv', 39 | vec4: '4fv', 40 | mat4: 'Matrix4fv' 41 | }; 42 | 43 | /** 44 | * @param {MiniGL} minigl 45 | * @param {string} type 46 | * @param {*} value 47 | * @param {object} properties 48 | */ 49 | constructor(minigl, type, value, properties = {}) { 50 | 51 | // Add additional properties i.e. excludeFrom, transpose... etc 52 | Object.assign(this, properties); 53 | 54 | // Set required properties. 55 | this.gl = minigl; 56 | this.type = type; 57 | this.value = value; 58 | 59 | // Get type function from map. 60 | this.typeFn = this._typeMap[this.type] || this._typeMap.float; 61 | 62 | // Update. 63 | this.update(); 64 | } 65 | 66 | update(value) { 67 | if (this.value) { 68 | 69 | var paramB = this.value; 70 | var paramC = null; 71 | 72 | if (this.typeFn.indexOf('Matrix') === 0) { 73 | paramB = this.transpose; 74 | paramC = this.value; 75 | } 76 | 77 | this.gl.getContext()[`uniform${this.typeFn}`](value, paramB, paramC); 78 | } 79 | } 80 | 81 | getDeclaration(name, type, length) { 82 | if (this.excludeFrom !== type) { 83 | 84 | if (this.type === 'array') { 85 | return `${this.value[0].getDeclaration(name, type, this.value.length)} 86 | const int ${name}_length = ${this.value.length};`; 87 | } 88 | 89 | if (this.type === 'struct') { 90 | let namePrefix = name.replace('u_', ''); 91 | namePrefix = namePrefix.charAt(0).toUpperCase() + namePrefix.slice(1); 92 | 93 | const declaration = Object.entries(this.value).map(([name, uniform]) => { 94 | return uniform.getDeclaration(name, type).replace(/^uniform/, ''); 95 | }).join(''); 96 | 97 | return `uniform struct ${namePrefix} { 98 | ${declaration} 99 | } ${name}${ length > 0 ? `[${length}]` : '' };`; 100 | 101 | } 102 | 103 | return `uniform ${this.type} ${name}${ length > 0 ? `[${length}]` : '' };` 104 | } 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | const mix = require( 'laravel-mix' ); 2 | 3 | mix.setPublicPath( './dist' ); 4 | mix.js( 'src/Gradient.js', 'stripe-gradient.js' ); 5 | --------------------------------------------------------------------------------