HTML preprocessors can make writing HTML more powerful or convenient. For instance, Markdown is designed to be easier to write and read for text documents and you could write a loop in Pug.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. So you don't have access to higher-up elements like the <html>
tag. If you want to add classes there that can affect the whole document, this is the place to do it.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. If you need things in the <head>
of the document, put that code here.
The resource you are linking to is using the 'http' protocol, which may not work when the browser is using https.
CSS preprocessors help make authoring CSS easier. All of them offer things like variables and mixins to provide convenient abstractions.
It's a common practice to apply CSS to a page that styles elements such that they are consistent across all browsers. We offer two of the most popular choices: normalize.css and a reset. Or, choose Neither and nothing will be applied.
To get the best cross-browser support, it is a common practice to apply vendor prefixes to CSS properties and values that require them to work. For instance -webkit-
or -moz-
.
We offer two popular choices: Autoprefixer (which processes your CSS server-side) and -prefix-free (which applies prefixes via a script, client-side).
Any URLs added here will be added as <link>
s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.
You can apply CSS to your Pen from any stylesheet on the web. Just put a URL to it here and we'll apply it, in the order you have them, before the CSS in the Pen itself.
You can also link to another Pen here (use the .css
URL Extension) and we'll pull the CSS from that Pen and include it. If it's using a matching preprocessor, use the appropriate URL Extension and we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
JavaScript preprocessors can help make authoring JavaScript easier and more convenient.
Babel includes JSX processing.
Any URL's added here will be added as <script>
s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.
You can apply a script from anywhere on the web to your Pen. Just put a URL to it here and we'll add it, in the order you have them, before the JavaScript in the Pen itself.
If the script you link to has the file extension of a preprocessor, we'll attempt to process it before applying.
You can also link to another Pen here, and we'll pull the JavaScript from that Pen and include it. If it's using a matching preprocessor, we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
Search for and use JavaScript packages from npm here. By selecting a package, an import
statement will be added to the top of the JavaScript editor for this package.
Using packages here is powered by esm.sh, which makes packages from npm not only available on a CDN, but prepares them for native JavaScript ESM usage.
All packages are different, so refer to their docs for how they work.
If you're using React / ReactDOM, make sure to turn on Babel for the JSX processing.
If active, Pens will autosave every 30 seconds after being saved once.
If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.
If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.
Visit your global Editor Settings.
<div id="world"></div>
<div id="instructions">Move your mouse <br/>to change speed and direction</div>
<div id="credits">
<p> <a href="https://codepen.io/Yakudoo/" target="blank">my other codepens</a> | <a href="https://epic.net" target="blank">www.epic.net</a></p>
</div>
@import url(https://fonts.googleapis.com/css?family=Open+Sans:800);
#world{
background: linear-gradient(#8ee4ae, #e9eba3);
position:absolute;
width:100%;
height:100%;
overflow:hidden;
}
#instructions{
position:absolute;
width:100%;
top:50%;
margin: auto;
margin-top:80px;
font-family:'Open Sans', sans-serif;
color:#71b583;
font-size:1.2em;
text-transform: uppercase;
text-align : center;
}
#credits{
position:absolute;
width:100%;
margin: auto;
bottom:0;
margin-bottom:20px;
font-family:'Open Sans', sans-serif;
color:#71b583;
font-size:0.7em;
text-transform: uppercase;
text-align : center;
}
#credits a {
color:#71b583;
}
//THREEJS RELATED VARIABLES
var scene,
camera,
fieldOfView,
aspectRatio,
nearPlane,
farPlane,
shadowLight,
light,
renderer,
container;
//SCREEN VARIABLES
var HEIGHT,
WIDTH,
windowHalfX,
windowHalfY,
xLimit,
yLimit;
// FISH BODY PARTS
var fish,
bodyFish,
tailFish,
topFish,
rightIris,
leftIris,
rightEye,
leftEye,
lipsFish,
tooth1,
tooth2,
tooth3,
tooth4,
tooth5;
// FISH SPEED
// the colors are splitted into rgb values to facilitate the transition of the color
var fishFastColor = {r:255, g:0, b:224}; // pastel blue
fishSlowColor = {r:0, g:207, b:255}; // purple
angleFin = 0; // angle used to move the fishtail
// PARTICLES COLORS
// array used to store a color scheme to randomly tint the particles
var colors = ['#dff69e',
'#00ceff',
'#002bca',
'#ff00e0',
'#3f159f',
'#71b583',
'#00a2ff'];
// PARTICLES
// as the particles are recycled, I use 2 arrays to store them
// flyingParticles used to update the flying particles and waitingParticles used to store the "unused" particles until we need them;
var flyingParticles = [];
waitingParticles = [];
// maximum z position for a particle
maxParticlesZ = 600;
// SPEED
var speed = {x:0, y:0};
var smoothing = 10;
// MISC
var mousePos = {x:0, y:0};
var stats;
var halfPI = Math.PI/2;
function init(){
// To work with THREEJS, you need a scene, a camera, and a renderer
// create the scene;
scene = new THREE.Scene();
// create the camera
HEIGHT = window.innerHeight;
WIDTH = window.innerWidth;
aspectRatio = WIDTH / HEIGHT;
fieldOfView = 60;
nearPlane = 1; // the camera won't "see" any object placed in front of this plane
farPlane = 2000; // the camera wont't see any object placed further than this plane
camera = new THREE.PerspectiveCamera(
fieldOfView,
aspectRatio,
nearPlane,
farPlane);
camera.position.z = 1000;
//create the renderer
renderer = new THREE.WebGLRenderer({alpha: true, antialias: true });
renderer.setSize(WIDTH, HEIGHT);
container = document.getElementById('world');
container.appendChild(renderer.domElement);
/*
As I will recycle the particles, I need to know the left and right limits they can fly without disappearing from the camera field of view.
As soon as a particle is out of the camera view, I can recycle it : remove it from the flyingParticles array and push it back in the waitingParticles array.
I guess I can do that by raycasting each particle each frame, but I think this will be too heavy. Instead I prefer to precalculate the x coordinate from which a particle is not visible anymore. But this depends on the z position of the particle.
Here I decided to use the furthest possible z position for a particle, to be sure that all the particles won't be recycled before they are out of the camera view. But this could be much more precise, by precalculating the x limit for each particle depending on its z position and store it in the particle when it is "fired". But today, I'll keep it simple :)
!!!!!! I'm really not sure this is the best way to do it. If you find a better solution, please tell me
*/
// convert the field of view to radians
var ang = (fieldOfView/2)* Math.PI / 180;
// calculate the max y position seen by the camera related to the maxParticlesZ position, I start by calculating the y limit because fielOfView is a vertical field of view. I then calculate the x Limit
yLimit = (camera.position.z + maxParticlesZ) * Math.tan(ang); // this is a formula I found, don't ask me why it works, it just does :)
// Calculate the max x position seen by the camera related to the y Limit position
xLimit = yLimit *camera.aspect;
// precalculate the center of the screen, used to update the speed depending on the mouse position
windowHalfX = WIDTH / 2;
windowHalfY = HEIGHT / 2;
// handling resize and mouse move events
window.addEventListener('resize', onWindowResize, false);
document.addEventListener('mousemove', handleMouseMove, false);
// let's make it work on mobile too
document.addEventListener('touchstart', handleTouchStart, false);
document.addEventListener('touchend', handleTouchEnd, false);
document.addEventListener('touchmove',handleTouchMove, false);
}
function onWindowResize() {
HEIGHT = window.innerHeight;
WIDTH = window.innerWidth;
windowHalfX = WIDTH / 2;
windowHalfY = HEIGHT / 2;
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix(); // force the camera to update its aspect ratio
// recalculate the limits
var ang = (fieldOfView/2)* Math.PI / 180;
yLimit = (camera.position.z + maxPartcilesZ) * Math.tan(ang);
xLimit = yLimit *camera.aspect;
}
function handleMouseMove(event) {
mousePos = {x:event.clientX, y:event.clientY};
updateSpeed()
}
function handleTouchStart(event) {
if (event.touches.length > 1) {
event.preventDefault();
mousePos = {x:event.touches[0].pageX, y:event.touches[0].pageY};
updateSpeed();
}
}
function handleTouchEnd(event) {
mousePos = {x:windowHalfX, y:windowHalfY};
updateSpeed();
}
function handleTouchMove(event) {
if (event.touches.length == 1) {
event.preventDefault();
mousePos = {x:event.touches[0].pageX, y:event.touches[0].pageY};
updateSpeed();
}
}
function updateSpeed(){
speed.x = (mousePos.x / WIDTH)*100;
speed.y = (mousePos.y-windowHalfY) / 10;
}
function loop() {
// Update fish position, rotation, scale... depending on the mouse position
// To make a smooth update of each value I use this formula :
// currentValue += (targetValue - currentValue) / smoothing
// make the fish swing according to the mouse direction
fish.rotation.z += ((-speed.y/50)-fish.rotation.z)/smoothing;
fish.rotation.x += ((-speed.y/50)-fish.rotation.x)/smoothing;
fish.rotation.y += ((-speed.y/50)-fish.rotation.y)/smoothing;
// make the fish move according to the mouse direction
fish.position.x += (((mousePos.x - windowHalfX)) - fish.position.x) / smoothing;
fish.position.y += ((-speed.y*10)-fish.position.y)/smoothing;
// make the eyes follow the mouse direction
rightEye.rotation.z = leftEye.rotation.z = -speed.y/150;
rightIris.position.x = leftIris.position.y = -10 - speed.y/2;
// make it look angry when the speed increases by narrowing the eyes
rightEye.scale.set(1,1-(speed.x/150),1);
leftEye.scale.set(1,1-(speed.x/150),1);
// in order to optimize, I precalculate a smaller speed values depending on speed.x
// these variables will be used to update the wagging of the tail, the color of the fish and the scale of the fish
var s2 = speed.x/100; // used for the wagging speed and color
var s3 = speed.x/300; // used for the scale
// I use an angle that I increment, and then use its cosine and sine to make the tail wag in a cyclic movement. The speed of the wagging depends on the global speed
angleFin += s2;
// for a better optimization, precalculate sine and cosines
var backTailCycle = Math.cos(angleFin);
var sideFinsCycle = Math.sin(angleFin/5);
tailFish.rotation.y = backTailCycle*.5;
topFish.rotation.x = sideFinsCycle*.5;
sideRightFish.rotation.x = halfPI + sideFinsCycle*.2;
sideLeftFish.rotation.x = halfPI + sideFinsCycle*.2;
// color update depending on the speed
var rvalue = (fishSlowColor.r + (fishFastColor.r - fishSlowColor.r)*s2)/255;
var gvalue = (fishSlowColor.g + (fishFastColor.g - fishSlowColor.g)*s2)/255;
var bvalue = (fishSlowColor.b + (fishFastColor.b - fishSlowColor.b)*s2)/255;
bodyFish.material.color.setRGB(rvalue,gvalue,bvalue);
lipsFish.material.color.setRGB(rvalue,gvalue,bvalue);
//scale update depending on the speed => make the fish struggling to progress
fish.scale.set(1+s3,1-s3,1-s3);
// particles update
for (var i=0; i<flyingParticles.length; i++){
var particle = flyingParticles[i];
particle.rotation.y += (1/particle.scale.x) *.05;
particle.rotation.x += (1/particle.scale.x) *.05;
particle.rotation.z += (1/particle.scale.x) *.05;
particle.position.x += -10 -(1/particle.scale.x) * speed.x *.2;
particle.position.y += (1/particle.scale.x) * speed.y *.2;
if (particle.position.x < -xLimit - 80){ // check if the particle is out of the field of view
scene.remove(particle);
waitingParticles.push(flyingParticles.splice(i,1)[0]); // recycle the particle
i--;
}
}
renderer.render(scene, camera);
stats.update();
requestAnimationFrame(loop);
}
function createStats() {
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
stats.domElement.style.right = '0px';
container.appendChild(stats.domElement);
}
// Lights
// I use 2 lights, an hemisphere to give a global ambient light
// And a harder light to add some shadows
function createLight() {
light = new THREE.HemisphereLight(0xffffff, 0xffffff, .3)
scene.add(light);
shadowLight = new THREE.DirectionalLight(0xffffff, .8);
shadowLight.position.set(1, 1, 1);
scene.add(shadowLight);
}
function createFish(){
// A group that will contain each part of the fish
fish = new THREE.Group();
// each part needs a geometry, a material, and a mesh
// Body
var bodyGeom = new THREE.BoxGeometry(120, 120, 120);
var bodyMat = new THREE.MeshLambertMaterial({
color: 0x80f5fe ,
shading: THREE.FlatShading
});
bodyFish = new THREE.Mesh(bodyGeom, bodyMat);
// Tail
var tailGeom = new THREE.CylinderGeometry(0, 60, 60, 4, false);
var tailMat = new THREE.MeshLambertMaterial({
color: 0xff00dc,
shading: THREE.FlatShading
});
tailFish = new THREE.Mesh(tailGeom, tailMat);
tailFish.scale.set(.8,1,.1);
tailFish.position.x = -60;
tailFish.rotation.z = -halfPI;
// Lips
var lipsGeom = new THREE.BoxGeometry(25, 10, 120);
var lipsMat = new THREE.MeshLambertMaterial({
color: 0x80f5fe ,
shading: THREE.FlatShading
});
lipsFish = new THREE.Mesh(lipsGeom, lipsMat);
lipsFish.position.x = 65;
lipsFish.position.y = -47;
lipsFish.rotation.z = halfPI;
// Fins
topFish = new THREE.Mesh(tailGeom, tailMat);
topFish.scale.set(.8,1,.1);
topFish.position.x = -20;
topFish.position.y = 60;
topFish.rotation.z = -halfPI;
sideRightFish = new THREE.Mesh(tailGeom, tailMat);
sideRightFish.scale.set(.8,1,.1);
sideRightFish.rotation.x = halfPI;
sideRightFish.rotation.z = -halfPI;
sideRightFish.position.x = 0;
sideRightFish.position.y = -50;
sideRightFish.position.z = -60;
sideLeftFish = new THREE.Mesh(tailGeom, tailMat);
sideLeftFish.scale.set(.8,1,.1);
sideLeftFish.rotation.x = halfPI;
sideLeftFish.rotation.z = -halfPI;
sideLeftFish.position.x = 0;
sideLeftFish.position.y = -50;
sideLeftFish.position.z = 60;
// Eyes
var eyeGeom = new THREE.BoxGeometry(40, 40,5);
var eyeMat = new THREE.MeshLambertMaterial({
color: 0xffffff,
shading: THREE.FlatShading
});
rightEye = new THREE.Mesh(eyeGeom,eyeMat );
rightEye.position.z = -60;
rightEye.position.x = 25;
rightEye.position.y = -10;
var irisGeom = new THREE.BoxGeometry(10, 10,3);
var irisMat = new THREE.MeshLambertMaterial({
color: 0x330000,
shading: THREE.FlatShading
});
rightIris = new THREE.Mesh(irisGeom,irisMat );
rightIris.position.z = -65;
rightIris.position.x = 35;
rightIris.position.y = -10;
leftEye = new THREE.Mesh(eyeGeom,eyeMat );
leftEye.position.z = 60;
leftEye.position.x = 25;
leftEye.position.y = -10;
leftIris = new THREE.Mesh(irisGeom,irisMat );
leftIris.position.z = 65;
leftIris.position.x = 35;
leftIris.position.y = -10;
var toothGeom = new THREE.BoxGeometry(20, 4, 20);
var toothMat = new THREE.MeshLambertMaterial({
color: 0xffffff,
shading: THREE.FlatShading
});
// Teeth
tooth1 = new THREE.Mesh(toothGeom,toothMat);
tooth1.position.x = 65;
tooth1.position.y = -35;
tooth1.position.z = -50;
tooth1.rotation.z = halfPI;
tooth1.rotation.x = -halfPI;
tooth2 = new THREE.Mesh(toothGeom,toothMat);
tooth2.position.x = 65;
tooth2.position.y = -30;
tooth2.position.z = -25;
tooth2.rotation.z = halfPI;
tooth2.rotation.x = -Math.PI/12;
tooth3 = new THREE.Mesh(toothGeom,toothMat);
tooth3.position.x = 65;
tooth3.position.y = -25;
tooth3.position.z = 0;
tooth3.rotation.z = halfPI;
tooth4 = new THREE.Mesh(toothGeom,toothMat);
tooth4.position.x = 65;
tooth4.position.y = -30;
tooth4.position.z = 25;
tooth4.rotation.z = halfPI;
tooth4.rotation.x = Math.PI/12;
tooth5 = new THREE.Mesh(toothGeom,toothMat);
tooth5.position.x = 65;
tooth5.position.y = -35;
tooth5.position.z = 50;
tooth5.rotation.z = halfPI;
tooth5.rotation.x = Math.PI/8;
fish.add(bodyFish);
fish.add(tailFish);
fish.add(topFish);
fish.add(sideRightFish);
fish.add(sideLeftFish);
fish.add(rightEye);
fish.add(rightIris);
fish.add(leftEye);
fish.add(leftIris);
fish.add(tooth1);
fish.add(tooth2);
fish.add(tooth3);
fish.add(tooth4);
fish.add(tooth5);
fish.add(lipsFish);
fish.rotation.y = -Math.PI/4;
scene.add(fish);
}
// PARTICLES
function createParticle(){
var particle, geometryCore, ray, w,h,d, sh, sv;
// 3 different shapes are used, chosen randomly
var rnd = Math.random();
// BOX
if (rnd<.33){
w = 10 + Math.random()*30;
h = 10 + Math.random()*30;
d = 10 + Math.random()*30;
geometryCore = new THREE.BoxGeometry(w,h,d);
}
// TETRAHEDRON
else if (rnd<.66){
ray = 10 + Math.random()*20;
geometryCore = new THREE.TetrahedronGeometry(ray);
}
// SPHERE... but as I also randomly choose the number of horizontal and vertical segments, it sometimes lead to wierd shapes
else{
ray = 5+Math.random()*30;
sh = 2 + Math.floor(Math.random()*2);
sv = 2 + Math.floor(Math.random()*2);
geometryCore = new THREE.SphereGeometry(ray, sh, sv);
}
// Choose a color for each particle and create the mesh
var materialCore = new THREE.MeshLambertMaterial({
color: getRandomColor(),
shading: THREE.FlatShading
});
particle = new THREE.Mesh(geometryCore, materialCore);
return particle;
}
// depending if there is particles stored in the waintingParticles array, get one from there or create a new one
function getParticle(){
if (waitingParticles.length) {
return waitingParticles.pop();
}else{
return createParticle();
}
}
function flyParticle(){
var particle = getParticle();
// set the particle position randomly but keep it out of the field of view, and give it a random scale
particle.position.x = xLimit;
particle.position.y = -yLimit + Math.random()*yLimit*2;
particle.position.z = Math.random()*maxParticlesZ;
var s = .1 + Math.random();
particle.scale.set(s,s,s);
flyingParticles.push(particle);
scene.add(particle);
}
function getRandomColor(){
var col = hexToRgb(colors[Math.floor(Math.random()*colors.length)]);
var threecol = new THREE.Color("rgb("+col.r+","+col.g+","+col.b+")");
return threecol;
}
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
init();
createStats();
createLight();
createFish();
createParticle();
loop();
setInterval(flyParticle, 70); // launch a new particle every 70ms
Also see: Tab Triggers