<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.5.1/gsap.min.js" integrity="sha512-IQLehpLoVS4fNzl7IfH8Iowfm5+RiMGtHykgZJl9AWMgqx0AmJ6cRWcB+GaGVtIsnC4voMfm8f2vwtY+6oPjpQ==" crossorigin="anonymous"></script>
<div class="loading" id="js-loader"><div class="loader"></div></div>
<div class="wrapper">
<canvas id="c"></canvas>
</div>
body,
html {
margin: 0;
padding: 0;
background: #25252B;
}
* {
touch-action: manipulation;
}
*,
*:before,
*:after {
box-sizing: border-box;
}
body {
position: relative;
width: 100%;
height: 100vh;
}
.wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
#c {
position: absolute;
top: 0;
width: 100%;
height: 100%;
display: block;
}
.loading {
position: fixed;
z-index: 50;
width: 100%;
height: 100%;
top: 0; left: 0;
background: #f1f1f1;
display: flex;
justify-content: center;
align-items: center;
}
.loader{
-webkit-perspective: 120px;
-moz-perspective: 120px;
-ms-perspective: 120px;
perspective: 120px;
width: 100px;
height: 100px;
}
.loader:before{
content: "";
position: absolute;
left: 25px;
top: 25px;
width: 50px;
height: 50px;
background-color: #9bffaf;
animation: flip 1s infinite;
}
@keyframes flip {
0% {
transform: rotate(0);
}
50% {
transform: rotateY(180deg);
}
100% {
transform: rotateY(180deg) rotateX(180deg);
}
}
/*https://tympanus.net/codrops/2019/10/14/how-to-create-an-interactive-3d-character-with-three-js/*/
import * as THREE from "https://cdn.jsdelivr.net/npm/three@0.121.1/build/three.module.js";
import { GLTFLoader } from "https://cdn.jsdelivr.net/npm/three@0.121.1/examples/jsm/loaders/GLTFLoader.js";
(function () {
let scene,
renderer,
camera,
model,
neck,
waist,
possibleAnims,
mixer,
idle,
clock = new THREE.Clock(),
currentlyAnimating = false,
raycaster = new THREE.Raycaster(),
loaderAnim = document.getElementById("js-loader");
init();
function init() {
const MODEL_PATH =
"https://s3-us-west-2.amazonaws.com/s.cdpn.io/1376484/stacy_lightweight.glb";
const canvas = document.querySelector("#c");
const backgroundColor = 0xf1f1f1;
scene = new THREE.Scene();
scene.background = new THREE.Color(backgroundColor);
scene.fog = new THREE.Fog(backgroundColor, 60, 100);
renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.shadowMap.enabled = true;
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(
50,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, -3, 30);
let stacy_txt = new THREE.TextureLoader().load(
"https://s3-us-west-2.amazonaws.com/s.cdpn.io/1376484/stacy.jpg"
);
stacy_txt.flipY = false;
const stacy_mtl = new THREE.MeshPhongMaterial({
color: 0xffffff,
map: stacy_txt,
skinning: true
});
let loader = new GLTFLoader();
loader.load(
MODEL_PATH,
function (gltf) {
model = gltf.scene;
let fileAnimations = gltf.animations;
model.traverse((o) => {
if (o.isMesh) {
o.castShadow = true;
o.receiveShadow = true;
o.material = stacy_mtl;
}
if (o.isBone && o.name === "mixamorigNeck") {
neck = o;
}
if (o.isBone && o.name === "mixamorigSpine") {
waist = o;
}
});
model.scale.set(7, 7, 7);
model.position.y = -11;
scene.add(model);
loaderAnim.remove();
mixer = new THREE.AnimationMixer(model);
let idleAnim = THREE.AnimationClip.findByName(fileAnimations, "idle");
idleAnim.tracks.splice(3, 3);
idleAnim.tracks.splice(9, 3);
idle = mixer.clipAction(idleAnim);
idle.play();
},
undefined,
function (error) {
console.error(error);
}
);
let hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.61);
hemiLight.position.set(0, 50, 0);
scene.add(hemiLight);
let d = 8.25;
let dirLight = new THREE.DirectionalLight(0xffffff, 0.54);
dirLight.position.set(-8, 12, 8);
dirLight.castShadow = true;
dirLight.shadow.mapSize = new THREE.Vector2(1024, 1024);
dirLight.shadow.camera.near = 0.1;
dirLight.shadow.camera.far = 1500;
dirLight.shadow.camera.left = d * -1;
dirLight.shadow.camera.right = d;
dirLight.shadow.camera.top = d;
dirLight.shadow.camera.bottom = d * -1;
scene.add(dirLight);
let floorGeo = new THREE.PlaneGeometry(5000, 5000, 1, 1);
let floorMat = new THREE.MeshPhongMaterial({
color: 0xeeeeee,
shininess: 0
});
let floor = new THREE.Mesh(floorGeo, floorMat);
floor.rotation.x = -0.5 * Math.PI;
floor.receiveShadow = true;
floor.position.y = -11;
scene.add(floor);
let geometry = new THREE.SphereGeometry(8, 32, 32);
let material = new THREE.MeshBasicMaterial({ color: 0x9bffaf }); // 0xf2ce2e
let sphere = new THREE.Mesh(geometry, material);
sphere.position.z = -15;
sphere.position.y = -2.5;
sphere.position.x = -0.25;
scene.add(sphere);
}
function animate() {
if (mixer) {
mixer.update(clock.getDelta());
}
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
animate();
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
let width = window.innerWidth;
let height = window.innerHeight;
let canvasPixelWidth = canvas.width / window.devicePixelRatio;
let canvasPixelHeight = canvas.height / window.devicePixelRatio;
const needResize =
canvasPixelWidth !== width || canvasPixelHeight !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
document.addEventListener("mousemove", function (e) {
var mousecoords = getMousePos(e);
if (neck && waist) {
moveJoint(mousecoords, neck, 50);
moveJoint(mousecoords, waist, 30);
}
});
function getMousePos(e) {
return { x: e.clientX, y: e.clientY };
}
function moveJoint(mouse, joint, degreeLimit) {
let degrees = getMouseDegrees(mouse.x, mouse.y, degreeLimit);
joint.rotation.y = THREE.Math.degToRad(degrees.x);
joint.rotation.x = THREE.Math.degToRad(degrees.y);
}
function getMouseDegrees(x, y, degreeLimit) {
let dx = 0,
dy = 0,
xdiff,
xPercentage,
ydiff,
yPercentage;
let w = { x: window.innerWidth, y: window.innerHeight };
// Left (Rotates neck left between 0 and -degreeLimit)
// 1. If cursor is in the left half of screen
if (x <= w.x / 2) {
// 2. Get the difference between middle of screen and cursor position
xdiff = w.x / 2 - x;
// 3. Find the percentage of that difference (percentage toward edge of screen)
xPercentage = (xdiff / (w.x / 2)) * 100;
// 4. Convert that to a percentage of the maximum rotation we allow for the neck
dx = ((degreeLimit * xPercentage) / 100) * -1;
}
// Right (Rotates neck right between 0 and degreeLimit)
if (x >= w.x / 2) {
xdiff = x - w.x / 2;
xPercentage = (xdiff / (w.x / 2)) * 100;
dx = (degreeLimit * xPercentage) / 100;
}
// Up (Rotates neck up between 0 and -degreeLimit)
if (y <= w.y / 2) {
ydiff = w.y / 2 - y;
yPercentage = (ydiff / (w.y / 2)) * 100;
// Note that I cut degreeLimit in half when she looks up
dy = ((degreeLimit * 0.5 * yPercentage) / 100) * -1;
}
// Down (Rotates neck down between 0 and degreeLimit)
if (y >= w.y / 2) {
ydiff = y - w.y / 2;
yPercentage = (ydiff / (w.y / 2)) * 100;
dy = (degreeLimit * yPercentage) / 100;
}
return { x: dx, y: dy };
}
})(); // Don't add anything below this line
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.