<div id="blob">
<canvas></canvas>
</div>
<div class="controls">
<div>
<label>Speed</label>
<input type="range" min="10" max="120" value="15" step="1" name="speed">
</div>
<div>
<label>Spikes</label>
<input type="range" min=".05" max="2" value=".5" step=".05" name="spikes">
</div>
<div>
<label>Processing</label>
<input type="range" min=".6" max="2.4" value="1" step=".01" name="processing">
</div>
</div>
#blob {
position: relative;
canvas {
width: 1000px;
margin-top: -7%;
@media(max-width: 1200px) {
margin-top: -10%;
width: 800px
}
@media(max-width: 600px) {
width: 800px
}
}
}
.controls {
background: #3F4656;
display: flex;
padding: 20px;
border-radius: 10px;
position: relative;
z-index: 3;
box-shadow: 0 4px 20px -1px rgba(18, 22, 33, .7);
@media(max-width: 1200px) {
margin-top: -4%;
}
@media(max-width: 600px) {
flex-direction: column;
}
label {
color: #CDD9ED;
font-weight: 500;
font-size: 14px;
display: block;
margin-bottom: 16px;
@media(max-width: 600px) {
margin-bottom: 12px;
}
}
[type="range"] {
width: 160px;
@media(max-width: 600px) {
width: 280px;
}
}
& > div {
&:not(:last-child) {
margin-right: 20px;
@media(max-width: 600px) {
margin: 0 0 24px 0;
}
}
}
}
.rangeSlider {
position: relative;
background: none;
border: 1px solid #fff;
border-radius: 6px;
cursor: pointer;
&.rangeSlider__horizontal {
height: 10px;
width: 160px;
}
.rangeSlider__fill {
border-radius: 7px;
background: #fff;
position: absolute;
&:before {
content: '';
left: -2px;
top: -2px;
bottom: -2px;
right: -2px;
border: 2px solid #3F4656;
border-radius: 6px;
position: absolute;
}
}
.rangeSlider__fill__horizontal {
height: 100%;
top: 0;
left: 0;
}
.rangeSlider__handle {
border: 2px solid #3F4656;
cursor: grab;
cursor: -moz-grab;
cursor: -webkit-grab;
display: inline-block;
width: 22px;
height: 22px;
position: absolute;
background: white;
border-radius: 50%;
}
.rangeSlider__handle__horizontal {
top: -7px;
}
}
html {
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
}
* {
box-sizing: inherit;
&:before,
&:after {
box-sizing: inherit;
}
}
html,
body {
overflow: hidden;
}
// Center & dribbble
body {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-family: 'Source Sans Pro', Arial;
background: #1e1b34;
.dribbble {
position: fixed;
display: block;
right: 20px;
bottom: 20px;
img {
display: block;
height: 28px;
}
}
}
View Compiled
for (let i = 0, element; element = document.querySelectorAll('input[type="range"]')[i++];) {
rangeSlider.create(element, {
polyfill: true
});
}
// Bubble Shaders
const BUBBLE_VERTEX_SHADER = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`;
const BUBBLE_FRAGMENT_SHDAER = `
uniform vec3 color1;
uniform vec3 color2;
varying vec2 vUv;
void main() {
gl_FragColor = vec4(mix(color1, color2, vUv.y), 1.0);
}
`;
// Glow Shaders
const GLOW_VERTEX_SHADERS = `
varying vec3 vNormal;
void main() {
vec3 vNormal = normalize( normalMatrix * normal );
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`;
const GLOW_FRAGMENT_SHADERS = `
varying vec3 vNormal;
void main() {
float intensity = pow( 0.7 - dot( vNormal, vec3( 0.0, 0.0, 1.0 ) ), 4.0 );
gl_FragColor = vec4( 1.0, 1.0, 1.0, 1.0 ) * intensity;
}
`;
$(document).ready(function () {
// Range SLiders
const speedSlider = $('input[name="speed"]');
const spikesSlider = $('input[name="spikes"]');
const processingSlider = $('input[name="processing"]');
// Scene & Camera & Renderer
const $canvas = $('#blob canvas'),
canvas = $canvas[0],
renderer = new THREE.WebGLRenderer({
canvas: canvas,
context: canvas.getContext('webgl2'),
antialias: true,
alpha: true
}),
simplex = new SimplexNoise();
renderer.setSize($canvas.width(), $canvas.height());
renderer.setPixelRatio(window.devicePixelRatio || 1);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, $canvas.width() / $canvas.height(), 0.1, 1000);
camera.position.z = 5;
// Top Light
const lightTop = new THREE.DirectionalLight(0xFFFFFF, .7);
lightTop.position.set(0, 500, 200);
lightTop.castShadow = true;
scene.add(lightTop);
// Bottom Light
const lightBottom = new THREE.DirectionalLight(0xFFFFFF, .25);
lightBottom.position.set(0, -500, 400);
lightBottom.castShadow = true;
scene.add(lightBottom);
// Ambient Light
const ambientLight = new THREE.AmbientLight(0x798296);
scene.add(ambientLight);
// Bubble
const bubbleGeometry = new THREE.CircleGeometry(20, 128, 6, 6.3);
const bubbleMaterial = new THREE.ShaderMaterial({
uniforms: {
color1: {
value: new THREE.Color("#FE390C")
},
color2: {
value: new THREE.Color("#FACE40")
}
},
vertexShader: BUBBLE_VERTEX_SHADER,
fragmentShader: BUBBLE_FRAGMENT_SHDAER,
});
const bubble = new THREE.Mesh(bubbleGeometry, bubbleMaterial);
scene.add(bubble);
// Buble Glow
const glowMaterial = new THREE.ShaderMaterial({
uniforms: {
c: { type: "f", value: 1.0 },
p: { type: "f", value: 1.4 },
color1: {
value: new THREE.Color("#FE390C")
},
color2: {
value: new THREE.Color("#FACE40")
},
viewVector: { type: "v3", value: camera.position },
},
vertexShader: GLOW_VERTEX_SHADERS,
fragmentShader: GLOW_FRAGMENT_SHADERS,
side: THREE.FrontSide,
blending: THREE.AdditiveBlending,
transparent: true,
})
const glow = new THREE.Mesh(bubbleGeometry.clone(), glowMaterial);
glow.position = bubble.position;
glow.scale.multiplyScalar(1.1);
// scene.add(glow);
// Update
const update = () => {
const time = performance.now() * 0.00001 * speedSlider.val() * Math.pow(processingSlider.val(), 3),
spikes = spikesSlider.val() * processingSlider.val();
for (let i = 0; i < bubble.geometry.vertices.length; i++) {
const b = bubble.geometry.vertices[i]; // bubble vertices
// const g = glow.geometry.vertices[i]; // glow vertices
b.normalize().multiplyScalar(1 + 0.3 * simplex.noise4D(b.x * spikes, b.y * spikes, b.z * spikes + time, 3));
// g.normalize().multiplyScalar(1 + 0.3 * simplex.noise4D(g.x * spikes, g.y * spikes, g.z * spikes + time, 3));
}
// glow.material.uniforms.viewVector.value = new THREE.Vector3().subVectors(
// camera.position,
// glow.position
// );
bubble.geometry.computeVertexNormals();
bubble.geometry.normalsNeedUpdate = true;
bubble.geometry.verticesNeedUpdate = true;
// glow.geometry.computeVertexNormals();
// glow.geometry.normalsNeedUpdate = true;
// glow.geometry.verticesNeedUpdate = true;
}
// Animate
function animate() {
update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
});
This Pen doesn't use any external CSS resources.