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.
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<div id="container" role="img" aria-label="Ghost flying around a tree">
</div>
<div id="loader" >
<div id="loader-content" class="border-gradient border-gradient-purple">
<p>Loading 3D scene... ✨</p>
</div>
</div>
<button id="btn-freeze">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 99.799 42.739"><defs><filter height="1.248" y="-.124" width="1.092" x="-.046" id="a" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation="1.807"/></filter></defs><g
id="freeze-text-blur" transform="translate(-1.18 -1.623)" filter="url(#a)" fill="none" stroke="#b0b0b0"><path d="M25.636 10.552c-1.843 7.337-4.62 16.543-8.126 22.679M8.816 19.624c9.974-.372 30.667-3.216 33.64-14.363M15.053 24.726c5.363 1.364 13.934-2.697 16.442-4.535M31.022 27.845c1.609-1.694 3.847-2.611 5.576-4.158 6.37-5.7 1.953-7.319-1.418-1.701-3.897 6.496 7.813-.468 9.45-2.268M44.63 19.718c-4.05.891-13.429 16.197-2.74 9.072"/><path d="M42.078 26.805c2.818-.666 4.975-1.844 7.37-3.213 10.356-5.917 6.352-12.377-3.118-.756-8.242 10.116 2.227 8.924 8.316 2.835"/><path d="M52.19 25.766c2.445-1.012 4.868-2.107 7.18-3.402 9.618-5.386 3.297-8.388-2.55.189-5.712 8.377 3.361 8.05 8.598 3.685"/><path d="M64.284 23.498c1.292-.693 2.596-1.363 3.875-2.079 1.033-.578 1.86-1.403 2.645-2.268 4.807-5.287-4.933-.681-2.551 1.701 2.696 2.696 11.116-6.284 3.969 1.512-.322.35-5.82 5.654-6.142 5.008-.408-.816 3.932-2.844 4.724-2.646.867.217-.713 2.941 1.04 2.552 1.237-.275 2.219-1.27 3.118-2.08"/><path d="M73.828 23.498c3.162-.749 5.092-1.59 7.654-3.213 9.24-5.851-4.396-5.169-5.102 3.307-.416 4.986 10.12 2.11 12-.189M20.061 11.97c-5.567-2.034-10.471.077-14.552 4.157-2.638 2.639-2.336 5.286 1.7 5.67 1.577.15 6.195-1.06 4.726-.472-4.195 1.677-.003 1.448 2.173.756"/><path d="M79.309 28.128c2.9.636 5.15.384 7.843-.472 5.975-1.902 9.454-9.107 5.575-6.52-3.217 2.144 3.86 1.413 5.197.661M18.41 26.488c-3.197 8.188-9.445 15.54-11.16 5.856"/><g><path d="M95.263 5.625c-2.024 3.49-7.167 6.601-13.293 5.438M90.832 4.215c-.295 3.694-2.873 8.353-5.304 10.54M82.172 6.43c3.077 3.028 9.31 5.886 12.554 5.707"/></g><g><path d="M28.935 28.72c-1.436 3.613-5.137 8.71-8.66 10.472M22.557 29.793c2.541 3.398 4.076 6.935 2.685 9.4M19.267 35.164c5.351.644 10.22-1.22 13.293-4.565"/></g></g><g id="freeze-text-top" fill="none" stroke="#b0b0b0"><g stroke-width=".5"><path d="M24.457 8.93c-1.843 7.336-4.62 16.543-8.126 22.678M7.637 18c9.974-.371 30.667-3.215 33.64-14.362M13.874 23.104c5.363 1.363 13.934-2.697 16.442-4.536M29.843 26.222c1.608-1.694 3.847-2.611 5.575-4.158 6.37-5.7 1.954-7.319-1.417-1.7-3.898 6.495 7.813-.468 9.45-2.269M43.45 18.095c-4.049.892-13.428 16.197-2.74 9.072"/><path d="M40.899 25.182c2.818-.665 4.975-1.843 7.37-3.212 10.356-5.918 6.352-12.378-3.118-.756-8.242 10.115 2.227 8.923 8.316 2.834"/><path d="M51.01 24.143c2.446-1.012 4.87-2.107 7.181-3.402 9.618-5.385 3.297-8.388-2.55.19-5.713 8.376 3.361 8.048 8.598 3.684"/><path d="M63.105 21.875c1.292-.693 2.596-1.363 3.874-2.079 1.033-.578 1.86-1.403 2.646-2.268 4.807-5.287-4.933-.681-2.551 1.701 2.696 2.696 11.115-6.284 3.969 1.512-.322.351-5.82 5.655-6.142 5.008-.409-.816 3.932-2.843 4.724-2.645.867.216-.713 2.94 1.04 2.55 1.236-.274 2.218-1.268 3.118-2.078"/><path d="M72.649 21.875c3.162-.749 5.092-1.59 7.654-3.213 9.24-5.851-4.396-5.168-5.103 3.308-.415 4.985 10.12 2.11 12.001-.19M18.882 10.347c-5.568-2.034-10.472.077-14.552 4.158-2.638 2.638-2.336 5.285 1.7 5.67 1.576.15 6.195-1.061 4.725-.473-4.194 1.677-.002 1.448 2.174.756"/><path d="M78.13 26.505c2.9.636 5.15.384 7.843-.472 5.975-1.901 9.454-9.106 5.575-6.52-3.217 2.144 3.86 1.414 5.197.661"/></g><path d="M17.23 24.865c-3.196 8.189-9.444 15.54-11.159 5.857" stroke-width=".5"/><g stroke-width=".3" stroke="#cb02ea"><path d="M94.084 4.002c-2.024 3.49-7.167 6.601-13.293 5.438M89.653 2.593c-.295 3.693-2.873 8.352-5.304 10.54M80.993 4.808c3.077 3.028 9.31 5.885 12.554 5.706"/></g><g stroke-width=".3" stroke="#cb02ea"><path d="M27.755 27.096c-1.435 3.614-5.136 8.712-8.66 10.473M21.378 28.17c2.541 3.399 4.076 6.935 2.685 9.4M18.088 33.541c5.351.644 10.22-1.219 13.293-4.565"/></g></g></svg>
</button>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.158.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.158.0/examples/jsm/"
}
}
</script>
<!-- Matcap VS -->
<script type="x-shader/x-vertex" id="matcap-vs">
varying vec2 vN;
void main() {
vec3 e = normalize( vec3( modelViewMatrix * vec4( position, 1.0 ) ) );
vec3 n = normalize( normalMatrix * normal );
vec3 r = reflect( e, n );
float m = 2. * sqrt( pow( r.x, 2. ) + pow( r.y, 2. ) + pow( r.z + 1., 2. ) );
vN = r.xy / m + .5;
gl_Position = projectionMatrix * modelViewMatrix * vec4( vec3(position), 1. );
}
</script>
<!-- Matcap FS -->
<script type="x-shader/x-fragment" id="matcap-fs">
uniform sampler2D tMatCap;
varying vec2 vN;
void main() {
vec3 base = texture2D( tMatCap, vN ).rgb;
gl_FragColor = vec4( base, 1.0 );
}
</script>
<!-- Flaky VS -->
<script type="x-shader/x-vertex" id="flaky-vs">
// Simplex 4D Noise
// by Ian McEwan, Ashima Arts
//
vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}
float permute(float x){return floor(mod(((x*34.0)+1.0)*x, 289.0));}
vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}
float taylorInvSqrt(float r){return 1.79284291400159 - 0.85373472095314 * r;}
vec4 grad4(float j, vec4 ip){
const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0);
vec4 p,s;
p.xyz = floor( fract (vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0;
p.w = 1.5 - dot(abs(p.xyz), ones.xyz);
s = vec4(lessThan(p, vec4(0.0)));
p.xyz = p.xyz + (s.xyz*2.0 - 1.0) * s.www;
return p;
}
float snoise(vec4 v){
const vec2 C = vec2( 0.138196601125010504, // (5 - sqrt(5))/20 G4
0.309016994374947451); // (sqrt(5) - 1)/4 F4
// First corner
vec4 i = floor(v + dot(v, C.yyyy) );
vec4 x0 = v - i + dot(i, C.xxxx);
// Other corners
// Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI)
vec4 i0;
vec3 isX = step( x0.yzw, x0.xxx );
vec3 isYZ = step( x0.zww, x0.yyz );
// i0.x = dot( isX, vec3( 1.0 ) );
i0.x = isX.x + isX.y + isX.z;
i0.yzw = 1.0 - isX;
// i0.y += dot( isYZ.xy, vec2( 1.0 ) );
i0.y += isYZ.x + isYZ.y;
i0.zw += 1.0 - isYZ.xy;
i0.z += isYZ.z;
i0.w += 1.0 - isYZ.z;
// i0 now contains the unique values 0,1,2,3 in each channel
vec4 i3 = clamp( i0, 0.0, 1.0 );
vec4 i2 = clamp( i0-1.0, 0.0, 1.0 );
vec4 i1 = clamp( i0-2.0, 0.0, 1.0 );
// x0 = x0 - 0.0 + 0.0 * C
vec4 x1 = x0 - i1 + 1.0 * C.xxxx;
vec4 x2 = x0 - i2 + 2.0 * C.xxxx;
vec4 x3 = x0 - i3 + 3.0 * C.xxxx;
vec4 x4 = x0 - 1.0 + 4.0 * C.xxxx;
// Permutations
i = mod(i, 289.0);
float j0 = permute( permute( permute( permute(i.w) + i.z) + i.y) + i.x);
vec4 j1 = permute( permute( permute( permute (
i.w + vec4(i1.w, i2.w, i3.w, 1.0 ))
+ i.z + vec4(i1.z, i2.z, i3.z, 1.0 ))
+ i.y + vec4(i1.y, i2.y, i3.y, 1.0 ))
+ i.x + vec4(i1.x, i2.x, i3.x, 1.0 ));
// Gradients
// ( 7*7*6 points uniformly over a cube, mapped onto a 4-octahedron.)
// 7*7*6 = 294, which is close to the ring size 17*17 = 289.
vec4 ip = vec4(1.0/294.0, 1.0/49.0, 1.0/7.0, 0.0) ;
vec4 p0 = grad4(j0, ip);
vec4 p1 = grad4(j1.x, ip);
vec4 p2 = grad4(j1.y, ip);
vec4 p3 = grad4(j1.z, ip);
vec4 p4 = grad4(j1.w, ip);
// Normalise gradients
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
p0 *= norm.x;
p1 *= norm.y;
p2 *= norm.z;
p3 *= norm.w;
p4 *= taylorInvSqrt(dot(p4,p4));
// Mix contributions from the five corners
vec3 m0 = max(0.6 - vec3(dot(x0,x0), dot(x1,x1), dot(x2,x2)), 0.0);
vec2 m1 = max(0.6 - vec2(dot(x3,x3), dot(x4,x4) ), 0.0);
m0 = m0 * m0;
m1 = m1 * m1;
return 49.0 * ( dot(m0*m0, vec3( dot( p0, x0 ), dot( p1, x1 ), dot( p2, x2 )))
+ dot(m1*m1, vec2( dot( p3, x3 ), dot( p4, x4 ) ) ) ) ;
}
#define NUM_OCTAVES 3
float fbm(vec4 x) {
float v = 0.0;
float a = 0.5;
vec4 shift = vec4(100.);
for (int i = 0; i < NUM_OCTAVES; ++i) {
v += a * snoise(x);
x = x * 2.0 + shift;
a *= 0.5;
}
return v;
}
float map(float value, float min1, float max1, float min2, float max2) {
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
}
uniform float uTime;
uniform float uEdgeWidth;
uniform float uFrequency;
varying float vNoise;
varying float vEdge;
varying vec3 vNormal;
varying vec3 vViewPosition;
varying vec2 vUv;
void main() {
vUv = uv;
vNoise = fbm(vec4(position*uFrequency, 0.));
vNoise = map(vNoise,-0.6,0.6,0.,1. - uEdgeWidth);
vec4 modelViewPosition = modelViewMatrix * vec4( position, 1.0 );
vViewPosition = -modelViewPosition.xyz;
gl_Position = projectionMatrix * modelViewPosition;
vNormal = normalMatrix * normal;
}
</script>
<!-- Flaky FS before main -->
<script type="x-shader/x-fragment" id="flaky-fs-beforeMain">
uniform float uThreshold;
uniform float uEdgeWidth;
uniform vec3 uEdgeColor;
uniform vec3 uColor;
varying float vNoise;
</script>
<!-- Flaky FS main -->
<script type="x-shader/x-fragment" id="flaky-fs">
float alpha = 1.0;
if( vNoise > uThreshold) {
alpha = 0.0;
}
vec3 materialColor = uColor / 255.0;
vec3 edgeColor = uEdgeColor / 255.0;
vec3 color = materialColor;
if( vNoise + uEdgeWidth > uThreshold ) {
color = edgeColor;
}
vec4 diffuseColor = vec4( color, alpha );
</script>
<!-- Glow VS -->
<script type="x-shader/x-vertex" id="glow-vs">
varying vec3 vVertexWorldPosition;
varying vec3 vVertexNormal;
varying vec4 vFragColor;
void main() {
vVertexNormal = normalize(normalMatrix * normal);
vVertexWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<!-- Glow FS -->
<script type="x-shader/x-fragment" id="glow-fs">
uniform vec3 glowColor;
uniform float coeficient;
uniform float power;
varying vec3 vVertexNormal;
varying vec3 vVertexWorldPosition;
varying vec4 vFragColor;
void main() {
vec3 worldCameraToVertex = vVertexWorldPosition - cameraPosition;
vec3 viewCameraToVertex = (viewMatrix * vec4(worldCameraToVertex, 0.0)).xyz;
viewCameraToVertex = normalize(viewCameraToVertex);
float intensity = pow(coeficient + dot(vVertexNormal, viewCameraToVertex), power);
gl_FragColor = vec4(glowColor, intensity);
}
</script>
<!-- Radial gradient VS -->
<script type="x-shader/x-vertex" id="radialGradient-vs">
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1.0);
}
</script>
<!-- Radial gradient FS -->
<script type="x-shader/x-fragment" id="radialGradient-fs">
varying vec2 vUv;
uniform vec3 color1;
uniform vec3 color2;
uniform float ratio;
void main() {
vec2 uv = (vUv - 0.5) * vec2(ratio, 1.0);
gl_FragColor = vec4(mix(color1, color2, length(uv)), 1.0);
}
</script>
body {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
overflow: hidden;
background: rgb(82,81,79);
background: radial-gradient(circle, rgba(82,81,79,1) 0%, rgba(36,35,33,1) 32%, rgba(0,0,0,1) 100%);
color: #adadad;
font-family: Arial;
font-size: 16px;
-ms-touch-action: none;
touch-action: none;
}
#container {
position: absolute;
opacity: 0.25;
}
#loader {
width: inherit;
height: inherit;
margin: 0 auto;
position: absolute;
display: flex;
align-items: center;
justify-content: center;
}
#loader-content {
background: none;
}
button {
position: absolute;
background: none;
border: none;
outline: none;
transition: transform 0.2s ease-in-out;
opacity: 1.0;
}
#btn-freeze {
position: absolute;
width: 145px;
height: auto;
cursor: pointer;
top: 0;
right: 0;
transform: rotate(3deg);
padding: 1.25em 1.0em;
opacity: 0.85;
}
#btn-freeze:hover {
animation: rotateMe2 0.35s ease-in-out forwards;
}
#btn-credits {
position: absolute;
width: 100px;
height: auto;
background: none;
bottom: 0;
right: 0;
opacity: 0.75;
padding: 0.5em 0.25em;
transform: rotate(-20deg);
display: none;
opacity: 0.7;
text-decoration: none;
}
a:active, a:focus {
text-decoration: none;
outline: 0;
border: none;
-moz-outline-style: none;
}
#btn-credits:hover {
animation: rotateMe 0.25s ease-in-out forwards;
}
@keyframes fadeOut {
0% { opacity: 1; }
100% { opacity: 0; }
}
@-moz-keyframes fadeOut {
0% { opacity: 1; }
100% { opacity: 0; }
}
@keyframes fadeIn {
0% { opacity: 0.25; }
100% { opacity: 1; }
}
@-moz-keyframes fadeIn {
0% { opacity: 0.25; }
100% { opacity: 1; }
}
@keyframes rotateMe {
0% { transform: rotate(-20deg);
opacity: 0.7;}
100% { transform: rotate(0deg);
opacity: 1;}
}
@-moz-keyframes rotateMe {
0% { transform: rotate(-20deg);
opacity: 0.7;}
100% { transform: rotate(0deg); opacity: 1.0; }
}
@keyframes rotateMe2 {
0% {
transform: rotate(3deg) scale(1.0);
opacity: 0.7;
}
100% {
transform: rotate(15deg) scale(1.05);
opacity: 1.0;
}
}
@-moz-keyframes rotateMe2 {
0% {
transform: rotate(3deg);
transform: scale(1.0);
opacity: 0.7;
}
100% {
transform: rotate(15deg);
transform: scale(1.1);
opacity: 1.0;
}
}
@media ( max-width: 800px ) {
#btn-freeze {
width: 120px;
position: fixed;
top: 0;
left: inherit;
right: 0;
padding: 1em;
transform: rotate(15deg);
}
#btn-freeze:hover {
animation: none;
}
#btn-credits {
display: none;
}
}
/*
UPDATED VERSION: NOVEMBER 2023, threejs v0.158.0
It's disco time!
And this ghost wants to party all night long 👻
But you can freeze him anytime you wish to!
★ Scene designed & coded by Anna Scavenger in May 2020 ★
https://twitter.com/ouchpixels
License: You can remix, adapt, and build upon my code non-commercially.
Assets credits, special thanks to:
Cubemap by Emil Persson aka Humus (@_Humus_)
Simplex noise by Stefan Gustavson (@stegu)
Tree gtlf model - by Helindu, modified by me in Blender:
https://sketchfab.com/3d-models/tree-without-leaves-1-336d3bc197ce4618ab325e7a6dfa0e7a
*/
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { Flow } from 'three/addons/modifiers/CurveModifier.js';
import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';
// ASSETS
let texChromeURL = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/911157/matcap_chromeEye_128.jpg";
let texSteelURL = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/911157/matcap_steel_256.jpg";
let ghostURL = "https://assets.codepen.io/911157/ghost1.glb";
let treeURL = "https://assets.codepen.io/911157/tree_ghost_opt.glb";
let scene, camera, renderer;
// Through these points flies the ghost
// Also points for placing the tori
let controlPoints = [
new THREE.Vector3(420, 120, 0), // letter D
new THREE.Vector3(-70, 200, 750), // additional point
new THREE.Vector3(-440, 100, 150), // letter G
new THREE.Vector3(-250, 440, -395), // letter O
new THREE.Vector3(-20, 600, -590), // additional point
new THREE.Vector3(200, 500, -380) // letter O2
];
let materials;
let geometries;
// FLOW + GLOW
let ghost;
let flow;
let glowMeshes = [];
let currentGlowMesh;
// GLOBAL MESHES
let discoBall, discoBallN, ballCover, ballCover2, discoBallG, discoBallT;
let letterHLeft, letterHRight, cylinderT;
let lettersNightGroup;
let hookG, hookD, starD, starG;
let pointLight, pointLight2;
// 'FREEZING' THE SCENE EFFECT
let quaternion = new THREE.Quaternion();
let isSceneFrozen = false;
let rotSpeed = 0.25;
const btnFreeze = document.querySelector('#btn-freeze');
const freezeTextBlur = document.querySelector('#freeze-text-blur');
const freezeTextTop = document.querySelector('#freeze-text-top');
// LANDSCAPE / PORTRAIT
let isMobile = /(Android|iPhone|iOS|iPod|iPad)/i.test(navigator.userAgent);
let windowRatio = window.innerWidth / window.innerHeight;
let isLandscape = (windowRatio > 1) ? true : false;
let mouseX = 0;
let mouseY = 0;
let params = {
terrainWidth: 2000,
terrainLength: isLandscape ? 3000 : (2500 / windowRatio),
terrainPosY: -250,
treeMainPos: new THREE.Vector3(0, -250, 0),
treesBackgroundPos: new THREE.Vector3(0, -250, 0),
numTrees: 7,
terrainLettersPosZ: isLandscape ? 850 : 950,
ghostColor: new THREE.Color(0xffffff),
glowOffColor: new THREE.Color(0x8D8D8D),
glowOnColor: new THREE.Color(0xca00e5),
pointLightColor: new THREE.Color(0x0fc4b8), // dark green
play: false,
}
init();
function init() {
geometries = initGeometries();
materials = initMaterials();
// CAMERA, LIGHTS, RENDERER
initBasicSetup();
// MESHES
initTerrain();
initTerrainLetters();
initAirLetters();
loadTree();
loadGhost();
render();
window.addEventListener("resize", onWindowResize, false);
document.addEventListener("mousemove", onMouseMove, false);
btnFreeze.addEventListener("click", freezeScene, false);
}
function initBasicSetup() {
initScene();
initCamera();
initLights();
initRenderer();
}
function initScene() {
scene = new THREE.Scene();
const fogNear = isLandscape ? 1200 : 2200;
const fogFar = isLandscape ? 4000 : 5500;
scene.fog = new THREE.Fog(0x998a7d, fogNear, fogFar);
}
function initCamera() {
camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 5000);
const cameraY = isLandscape ? 170 : 400;
const cameraZ = isLandscape ? 1850 : (1800 / windowRatio);
camera.position.set(0, cameraY, cameraZ);
camera.lookAt(0, 0, 0);
scene.add(camera);
}
function initLights() {
const backLight = new THREE.DirectionalLight(0xff00ff, 1.75 * Math.PI);
backLight.position.set(200, 0, -55);
backLight.target.position.set(300, 50, -100);
const fillLight = new THREE.DirectionalLight(0xffffff, 0.65 * Math.PI);
fillLight.position.set(80, 10, 200);
fillLight.target.position.set(50, 50, -50);
let posZ = isLandscape ? 750 : 850;
pointLight = new THREE.PointLight(params.pointLightColor, 8.5 * Math.PI, 200, 2);
pointLight.position.set(-300, 0, posZ);
pointLight2 = new THREE.PointLight(params.pointLightColor, 8.5 * Math.PI, 200, 2);
pointLight2.position.set(300, 0, posZ);
scene.add( fillLight, backLight, pointLight, pointLight2 );
}
function initRenderer() {
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(window.devicePixelRatio > 1.5 ? Math.min(window.devicePixelRatio, 1.4) : Math.min(window.devicePixelRatio, 1.25));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.gammaFactor = 2.2;
container.appendChild(renderer.domElement);
}
function initGeometries() {
let torus = new THREE.TorusGeometry(140, 40, 16, 46);
let halfTorus = new THREE.TorusGeometry(70, 25, 10, 20, Math.PI);
let cylinderThin = new THREE.CylinderGeometry(10, 10, 190, 8);
let cylinderThick = new THREE.CylinderGeometry(30, 30, 150, 22, 1);
let cylinderThick2 = new THREE.CylinderGeometry(30, 30, 150, 22, 22);
let sphere = new THREE.SphereGeometry(50, 24, 12);
let halfSphere = new THREE.SphereGeometry(50, 28, 8, 0, Math.PI);
let halfSphere2 = new THREE.SphereGeometry(55, 28, 8, 0, Math.PI);
let cone = new THREE.ConeGeometry(40, 110, 20);
// HOOK 1 - MERGED GEOMS
let halfTorusThin = new THREE.TorusGeometry( 85, 10, 12, 50, Math.PI);
halfTorusThin.rotateX(Math.PI);
let radius = halfTorusThin.parameters.radius;
let cylinderHeight = cylinderThin.parameters.height;
halfTorusThin.translate(- radius, - cylinderHeight / 2, 0);
let hook = mergeGeometries([halfTorusThin, cylinderThin], true);
// HOOK 2 - MERGED GEOMS
let cylinderD = new THREE.CylinderGeometry(10, 10, 350, 10);
let halfTorusD = new THREE.TorusGeometry(70, 10, 12, 50, Math.PI / 2);
halfTorusD.rotateX(Math.PI);
halfTorusD.rotateZ( - Math.PI / 2);
let radiusD = halfTorusD.parameters.radius;
let heightCylinderD = cylinderD.parameters.height;
halfTorusD.translate( radiusD, - heightCylinderD / 2, 0 );
let hook2 = mergeGeometries([cylinderD, halfTorusD], true);
return {
torus,
halfTorus,
cylinderThin,
cylinderThick,
cylinderThick2,
sphere,
halfSphere,
halfSphere2,
cone,
hook,
hook2
}
}
function initMaterials() {
const grey = new THREE.MeshBasicMaterial({color: 0x222222});
const blackBasic = new THREE.MeshBasicMaterial({color: 0x000000});
const brownSimple = isMobile ? (new THREE.MeshBasicMaterial({color: 0x594a3e})) : (new THREE.MeshLambertMaterial({color: 0x5e4732}));
const brown = new THREE.MeshLambertMaterial({color: 0x5e4732});
const purpleFlat = new THREE.MeshLambertMaterial({color: 0xff00ff});
const purple = new THREE.MeshStandardMaterial({
color: 0xff00ff,
roughness: 0.4,
metalness: 0.9
});
purple.color.convertSRGBToLinear();
const greyFlat = new THREE.MeshPhongMaterial({
color: 0x200066,
emissive: 0x54944,
specular: 0x1d683f,
shininess: 80,
flatShading: true
});
greyFlat.color.convertSRGBToLinear();
// MATCAPS
const textureLoader = new THREE.TextureLoader();
textureLoader.setCrossOrigin('');
const textureChrome = textureLoader.load(texChromeURL);
const textureSteel = textureLoader.load(texSteelURL);
const matcapChrome = new THREE.ShaderMaterial({
uniforms: {
tMatCap: { type: "t", value: textureChrome }
},
vertexShader: document.querySelector("#matcap-vs").textContent,
fragmentShader: document.querySelector("#matcap-fs").textContent,
side: THREE.DoubleSide
});
const matcapSteel = new THREE.ShaderMaterial({
uniforms: {
tMatCap: { type: "t", value: textureSteel }
},
vertexShader: document.querySelector("#matcap-vs").textContent,
fragmentShader: document.querySelector("#matcap-fs").textContent,
side: THREE.DoubleSide
});
// DISCO REFLECTIONS
const loader = new THREE.CubeTextureLoader();
loader.setCrossOrigin('');
loader.setPath('https://s3-us-west-2.amazonaws.com/s.cdpn.io/911157/');
const textureCube = loader.load([
'pisa1_px.jpg', 'pisa1_nx.jpg',
'pisa1_py.jpg', 'pisa1_ny.jpg',
'pisa1_pz.jpg', 'pisa1_nz.jpg'
]);
const disco = new THREE.MeshPhongMaterial({
envMap: textureCube,
color: 0xff00ff,
emissive: 0xaa00aa,
specular: 0x1d683f,
flatShading: true
});
// FLAKY MATERIAL
let phongShader = THREE.ShaderLib.standard;
let fragmentShader = phongShader.fragmentShader;
fragmentShader = document.querySelector("#flaky-fs-beforeMain").textContent +
fragmentShader.replace(
"vec4 diffuseColor = vec4( diffuse, opacity );",
document.querySelector("#flaky-fs").textContent
);
const flaky = new THREE.ShaderMaterial({
uniforms: THREE.UniformsUtils.merge([phongShader.uniforms, {
uThreshold: { value: 0.5 },
uEdgeWidth: { value: 0.0 },
uEdgeColor: { value: [255, 109, 203] },
uColor: { value: [255, 0, 255] },
uFrequency: { value: 0.95 }
}]),
vertexShader: document.querySelector("#flaky-vs").textContent,
fragmentShader: fragmentShader,
side: THREE.DoubleSide,
lights: true,
transparent: true
});
// GLOW MATERIAL
let glowOffColor = params.glowOffColor;
const glow = new THREE.ShaderMaterial({
uniforms: {
coeficient: { type: "f", value: 1.1 },
power: { type: "f", value: 1.4 },
glowColor: { type: "c", value: glowOffColor },
},
vertexShader: document.querySelector("#glow-vs").textContent,
fragmentShader: document.querySelector("#glow-fs").textContent,
transparent: true,
});
return {
blackBasic,
grey, greyFlat,
purple, purpleFlat,
brown, brownSimple,
matcapChrome, matcapSteel,
disco,
flaky,
glow
}
}
function initTerrain() {
let terrainWidth = params.terrainWidth;
let terrainLength = params.terrainLength;
let terrainY = params.terrainPosY;
let terrainGeom = new THREE.PlaneGeometry(terrainWidth, terrainLength, 30, 30);
terrainGeom.rotateX(-0.5 * Math.PI);
let positionAttribute = terrainGeom.attributes.position;
for (let i = 0; i < positionAttribute.count; i++) {
let x = positionAttribute.getX( i );
let y = positionAttribute.getY( i );
let z = positionAttribute.getZ( i );
y += Math.random() * 40;
positionAttribute.setXYZ(i, x, y, z);
}
const terrain = new THREE.Mesh(terrainGeom, materials.brown);
terrain.position.y = terrainY;
scene.add(terrain);
// SPRINKLE TERRAIN WITH POINTS
const positionArray = terrainGeom.attributes.position.array;
const positionAttributeCount = terrainGeom.attributes.position.count;
const pointsGeom = new THREE.BufferGeometry();
pointsGeom.setAttribute('position', new THREE.Float32BufferAttribute(positionArray, 3));
const pointsMat = new THREE.PointsMaterial({ color: 0xff00ff, size: 6 });
const terrainPoints = new THREE.Points(pointsGeom, pointsMat);
terrainPoints.position.y = terrainY;
scene.add(terrainPoints);
// SPRINKLE TERRAIN WITH LITTLE BOXES
const boxBufferGeom = new THREE.BoxGeometry(2.85, 2.85, 2.85);
const instancedBoxGeom = new THREE.InstancedBufferGeometry().copy(boxBufferGeom);
let numInstances = positionAttributeCount;
const terrainBoxes = new THREE.InstancedMesh(instancedBoxGeom, materials.purpleFlat, numInstances);
const dummy = new THREE.Object3D();
for (let i = 0; i < numInstances; i++) {
dummy.position.set(
noise.simplex3(i * 0.2, i * 0.5, 0.5) * 900,
Math.random() * (-220 - (-240)) + (-240),
Math.random() * (1300 - (-850)) + (-850),
);
dummy.rotation.set(
Math.random() * (Math.PI * 1.95 - (- Math.PI * 1.95)) + (-Math.PI * 1.95),
Math.random() * (Math.PI * 1.95 - (- Math.PI * 1.95)) + (-Math.PI * 1.95),
0.0
);
dummy.updateMatrix();
terrainBoxes.setMatrixAt(i, dummy.matrix);
}
scene.add(terrainBoxes);
}
function initTerrainLetters() {
let posZ = params.terrainLettersPosZ;
// letter N
const cylinderN = new THREE.Mesh(geometries.cylinderThick, materials.purple);
cylinderN.scale.y = 0.85;
cylinderN.position.set(-360, -150, posZ);
const cylinderN2 = new THREE.Mesh(geometries.cylinderThick, materials.purple);
cylinderN2.position.set(-230, -170, posZ);
const halfSphereN = new THREE.Mesh(geometries.halfSphere, materials.matcapSteel);
halfSphereN.position.set(-200.6, -97, posZ);
halfSphereN.scale.set(0.593, 0.593, 0.593);
halfSphereN.rotation.x = - Math.PI / 2;
scene.add(halfSphereN);
discoBallN = new THREE.Mesh(geometries.sphere, materials.disco);
discoBallN.position.set(-360, -50, posZ);
discoBallN.scale.set(0.5, 0.5, 0.5);
scene.add(discoBallN);
const cylinderThinnerN = new THREE.Mesh(geometries.cylinderThin, materials.matcapSteel);
cylinderThinnerN.position.set(-360, -150, posZ);
cylinderThinnerN.scale.set(0.4, 0.9, 0.4);
scene.add(cylinderThinnerN);
const cylinderThinN = new THREE.Mesh(geometries.cylinderThin, materials.matcapSteel);
cylinderThinN.position.set(-290, -170, posZ);
cylinderThinN.scale.set(1.2, 0.8, 1.2);
cylinderThinN.rotation.z = Math.PI / 4.5;
const discN = new THREE.Mesh( geometries.cylinderThick, materials.matcapSteel );
discN.position.set(-360, -223, posZ);
discN.scale.set(1.28, 0.125, 1.28);
const discN2 = discN.clone();
discN.position.set(-230, -240, posZ);
const letterN = new THREE.Group();
letterN.add(cylinderN, cylinderN2, cylinderThinN, discN, discN2);
scene.add(letterN);
// letter I
let letterIPosX = -110;
discoBall = new THREE.Mesh(geometries.sphere, materials.disco);
discoBall.position.set(letterIPosX, -70, posZ);
ballCover = new THREE.Mesh(geometries.halfSphere, materials.purple);
ballCover.material.side = THREE.DoubleSide;
let halfRadius = ballCover.geometry.parameters.radius;
ballCover.scale.set(1.15, 1.15, 1.15);
ballCover.position.set(letterIPosX, - 74 - halfRadius, posZ);
ballCover.rotation.set(Math.PI / 2, - Math.PI / 2, 0);
ballCover.geometry.applyMatrix4(new THREE.Matrix4().makeTranslation(- halfRadius, 0, 0));
ballCover.updateMatrix();
ballCover2 = ballCover.clone();
ballCover2.position.copy(ballCover.position);
ballCover2.rotation.z = Math.PI;
ballCover2.rotation.y = Math.PI / 2;
const cone = new THREE.Mesh(geometries.cone, materials.matcapSteel);
cone.position.set(letterIPosX, -180, posZ);
const letterI = new THREE.Group();
letterI.add(cone, discoBall, ballCover, ballCover2);
scene.add(letterI);
// letter G
const torusG = new THREE.Mesh(geometries.halfTorus, materials.purple);
torusG.position.set(50, -160, posZ);
torusG.rotation.z = Math.PI / 2;
const hookG = new THREE.Mesh(geometries.hook, materials.matcapSteel);
hookG.scale.set(0.3, 0.25, 0.8);
hookG.position.set(70, -185, posZ);
discoBallG = discoBallN.clone();
discoBallG.position.set(50, -160, posZ);
const letterG = new THREE.Group();
letterG.add(torusG, hookG, discoBallG);
scene.add(letterG);
// letter H
letterHLeft = new THREE.Group();
letterHRight = new THREE.Group();
const cylinderH = new THREE.Mesh(geometries.cylinderThick2, materials.flaky);
// cylinderH.position.set(140, -145, 850);
cylinderH.position.set(140, -160, posZ);
cylinderH.scale.set(1.0, 0.9, 1.0);
const cylinderH2 = new THREE.Mesh(geometries.cylinderThick, materials.matcapSteel);
cylinderH2.scale.set(0.98, 0.88, 0.98);
cylinderH2.position.copy(cylinderH.position);
const discH = new THREE.Mesh(geometries.cylinderThick, materials.matcapSteel);
discH.position.set(140, -87, posZ);
discH.scale.set(1.275, 0.08, 1.275);
const discH2 = discN.clone();
discH2.position.set(140, -225, posZ);
letterHLeft.add(cylinderH, cylinderH2, discH, discH2);
letterHRight = letterHLeft.clone();
letterHRight.position.set(120, 0, 0);
const horizontalH = new THREE.Mesh(geometries.cylinderThin, materials.matcapSteel);
horizontalH.scale.set(1.2, 0.5, 1.2);
horizontalH.rotation.z = Math.PI / 2;
horizontalH.position.set(200, -155, posZ);
const letterH = new THREE.Group();
letterH.add(letterHLeft, letterHRight, horizontalH);
scene.add(letterH);
// letter T
cylinderT = new THREE.Mesh(geometries.cylinderThin, materials.matcapSteel);
cylinderT.scale.set(0.55, 0.725, 0.55);
cylinderT.position.set(370, -40, posZ);
cylinderT.rotation.z = Math.PI / 2;
const cylinderT2 = cylinderN.clone();
cylinderT2.position.set(370, -170, posZ);
cylinderT2.scale.set(0.9, 0.75, 0.9);
discoBallT = discoBallN.clone();
discoBallT.position.set(370, -70, posZ);
const letterT = new THREE.Group();
letterT.add(cylinderT, cylinderT2, discoBallT);
scene.add(letterT);
lettersNightGroup = new THREE.Group();
}
function loadTree() {
const loader = new GLTFLoader();
loader.load(treeURL,
function (gltf) {
let tree = gltf.scene.children[0].children[0].children[0].children[0].children[0];
tree.geometry.computeVertexNormals();
let treeGeom = tree.geometry;
treeGeom.scale(55, 55, 55);
treeGeom.rotateX(-0.5 * Math.PI);
tree.position.set(50, -150, -1050);
tree.material = new THREE.MeshBasicMaterial({color: 0x000000, fog: false});
initTrees(treeGeom);
scene.add(tree);
},
function (xhr) { console.log((xhr.loaded / xhr.total * 100 ) + '% loaded'); },
function (error) { console.log( 'An error happened' ); }
);
}
function initTrees(tGeom) {
let treeMesh = new THREE.Mesh(tGeom, materials.matcapSteel);
let treesCoords = [
-350, -200, -1400,
-200, -270, -1300,
250, -250, -1300
];
for (let i = 0; i < (treesCoords.length / 3); i++) {
let t = treeMesh.clone();
let x = treesCoords[3 * i];
let y = treesCoords[3 * i + 1];
let z = treesCoords[3 * i + 2];
t.position.set(x, y, z);
t.rotation.set(0, i * 0.2 * Math.PI, 0);
scene.add(t);
}
}
function loadGhost() {
const loader = new GLTFLoader();
loader.load(ghostURL,
function (gltf) {
ghost = gltf.scene.children[2].parent.children[2];
ghost.geometry.computeVertexNormals();
initFlow();
removeLoadingScreen();
},
function (xhr) { console.log((xhr.loaded / xhr.total * 100 ) + '% loaded'); },
function (error) { console.log( 'An error happened' ); }
);
}
function initFlow() {
let ghostColor = params.ghostColor || 0xff3da4;
let purple = new THREE.MeshStandardMaterial({
color: ghostColor,
roughness: 0.95,
metalness: 0.05,
side: THREE.DoubleSide
});
purple.color.convertSRGBToLinear();
let curvePoints = controlPoints;
let curve = new THREE.CatmullRomCurve3(curvePoints);
curve.curveType = "centripetal";
curve.closed = true;
let ghostScale = 0.375;
const mesh = ghost;
mesh.geometry.scale(ghostScale, ghostScale, ghostScale);
mesh.geometry.rotateY(-Math.PI / 2);
mesh.geometry.rotateX(Math.PI / 2);
let objectToCurve = new THREE.Mesh(mesh.geometry, purple);
flow = new Flow(objectToCurve);
flow.updateCurve(0, curve);
scene.add(flow.object3D);
}
// "GOOD" LETTERS IN THE AIR
function initAirLetters() {
// 4 TORI - AIR LETTERS
let letterPositions = [controlPoints[0], controlPoints[2], controlPoints[3], controlPoints[5]];
let torus = new THREE.Mesh(geometries.torus, materials.glow);
for (let i = 0; i < letterPositions.length; i++) {
let t = torus.clone();
// IMPORTANT! When cloning a ShaderMaterial, the attributes and vertex/fragment programs are copied by reference.
// The uniforms are copied by value, which is what we need. / turning on and off glow for each mesh /
t.material = torus.material.clone();
t.material.isGlowing = false;
t.position.copy(letterPositions[i]);
glowMeshes.push(t);
scene.add(t);
}
// letter G
hookG = new THREE.Mesh(geometries.hook, materials.matcapChrome);
hookG.position.set(-310, -40, 150);
hookG.matrixAutoUpdate = false;
hookG.updateMatrix();
scene.add( hookG );
let starGeom = generateStarGeometry();
starG = new THREE.Mesh(starGeom, materials.matcapSteel);
starG.position.set(-480, -124, 150);
starG.rotation.z = Math.PI / 5;
starG.scale.set(2.4, 2.4, 2.4);
scene.add(starG);
// letter D
hookD = new THREE.Mesh(geometries.hook2, materials.matcapChrome);
hookD.position.set(550, 210, 0);
hookD.matrixAutoUpdate = false;
hookD.updateMatrix();
scene.add(hookD);
starD = starG.clone();
starD.position.set(550, 398, 0);
starD.rotation.z = Math.PI / 5;
scene.add(starD);
}
function generateStarGeometry() {
let numPoints = 7;
let minLength = 4;
let maxLength = 15;
let extrudeSettings = {
steps: 2,
depth: 2.5,
bevelEnabled: true,
bevelThickness: 0,
bevelSize: 2,
bevelOffset: 0,
bevelSegments: 1
};
let points = [];
for (let i = 0; i < numPoints * 2; i++) {
let l = i % 2 == 1 ? minLength : maxLength;
let step = i / numPoints * Math.PI;
points.push(new THREE.Vector2(Math.cos(step) * l, Math.sin(step) * l));
}
let starShape = new THREE.Shape(points);
let starGeom = new THREE.ExtrudeGeometry(starShape, extrudeSettings);
return starGeom;
}
function render() {
renderer.render(scene, camera);
requestAnimationFrame(render);
if (flow && isSceneFrozen) {
// STOP THE FLYING GHOST AND ROTATE THE SCENE
flow.moveAlongCurve(0.000);
rotateScene();
} else if (flow && !isSceneFrozen ) {
animateTerrainLetters();
updatePointLights();
flow.moveAlongCurve(0.005);
let offset = flow.uniforms.pathOffset.value.toFixed(3);
let progress = (offset - Math.floor(offset)).toFixed(3);
if (progress == 0.25) {
currentGlowMesh = glowMeshes[1];
updateMaterial(currentGlowMesh);
} else if (progress == 0.55) {
currentGlowMesh = glowMeshes[2];
updateMaterial(currentGlowMesh);
} else if (progress == 0.7) {
currentGlowMesh = glowMeshes[3];
updateMaterial(currentGlowMesh);
} else if (progress == 0.92) {
currentGlowMesh = glowMeshes[0];
updateMaterial(currentGlowMesh);
} else {
return;
}
}
}
function rotateScene() {
camera.lookAt(scene.position);
let angle = 0.001 * mouseX;
camera.position.applyQuaternion(quaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), angle));
}
function updateMaterial(mesh) {
let glowColor = mesh.material.uniforms.glowColor.value;
let coeff = mesh.material.uniforms.coeficient;
mesh.material.isGlowing = !mesh.material.isGlowing;
mesh.material.isGlowing ? glowColor.set(params.glowOnColor) : glowColor.set(params.glowOffColor);
mesh.material.isGlowing ? coeff.value = 1.4 : coeff.value = 1.0;
if (currentGlowMesh == glowMeshes[1]) {
mesh.material.isGlowing ? (hookG.material = materials.purple) : (hookG.material = materials.matcapChrome);
} else if (currentGlowMesh == glowMeshes[0]) {
mesh.material.isGlowing ? (hookD.material = materials.purple) : (hookD.material = materials.matcapChrome);
}
}
function updatePointLights() {
pointLight.intensity = 3.25 + Math.abs(mouseX) * 4.75;
pointLight2.intensity = 3.25 + Math.abs(mouseX) * 4.75;
}
function animateTerrainLetters() {
starG.rotation.y = mouseX * Math.PI;
starD.rotation.y = - mouseX * Math.PI;
// letter N
discoBallN.rotation.y = - (mouseX * mouseX) * Math.PI * 0.5;
// letter I
discoBall.rotation.y = mouseX * Math.PI * 0.25;
discoBall.material.emissive.r = 0.8 - Math.abs(mouseX * 0.78);
ballCover.rotation.y = - Math.PI / 2 + Math.PI / 1.85 * Math.abs(mouseX * mouseX);
ballCover.scale.set(1.15 - 0.6 * Math.abs(mouseX*mouseX), 1.15 - 0.85 * Math.abs(mouseX * mouseX), 1.15 - 0.75 * Math.abs(mouseX * mouseX));
ballCover2.rotation.y = Math.PI / 2 - Math.PI / 1.85 * Math.abs(mouseX * mouseX);
ballCover2.scale.set(1.15 - 0.6 * Math.abs(mouseX*mouseX), 1.15 - 0.85 * Math.abs(mouseX*mouseX), 1.15 - 0.75 * Math.abs(mouseX*mouseX));
// letter G
discoBallG.rotation.y = -mouseX * Math.PI;
discoBallG.scale.set(0.5, 0.2 + 0.3 * Math.abs(mouseX), 0.5);
// letter H
letterHLeft.children[0].rotation.y = (mouseX) * Math.PI;
letterHRight.children[0].rotation.y = - (mouseX) * Math.PI;
// letter T
discoBallT.rotation.y = -mouseX * Math.PI * 0.5;
cylinderT.rotation.y = -mouseX * Math.PI * 0.25;
}
// *** EVENTS ***
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onMouseMove(e) {
e.preventDefault();
mouseX = (event.clientX / window.innerWidth) * 2 - 1;
mouseY = -(event.clientY / window.innerHeight) * 2 + 1;
}
function freezeScene() {
params.play = false;
if (!isSceneFrozen) {
params.play = false;
isSceneFrozen = !isSceneFrozen;
freezeTextTop.style.stroke = "#63639f";
freezeTextBlur.style.stroke = "#5959ff";
} else {
params.play = true;
isSceneFrozen = !isSceneFrozen;
freezeTextBlur.style.stroke = "#b0b0b0";
freezeTextTop.style.stroke = "#b0b0b0";
}
}
function removeLoadingScreen() {
const loaderDiv = document.querySelector("#loader");
const sceneDiv = document.querySelector("#container");
loaderDiv.style.animation = "fadeOut 0.375s ease-in-out forwards";
sceneDiv.style.animation = "fadeIn 0.75s ease-in-out forwards";
loaderDiv.style.display = "none";
}
Also see: Tab Triggers