├── .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 |
--------------------------------------------------------------------------------