Compare commits
4 Commits
03117344a5
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20b4ec5327 | ||
|
|
595a890704 | ||
|
|
2f0c937248 | ||
|
|
a9604a8dc2 |
@@ -4,7 +4,9 @@
|
|||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
height: 100dvh;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
|
min-height: 100dvh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -12,7 +14,6 @@ html, body {
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: #0e0e0e;
|
background-color: #0e0e0e;
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -40,9 +41,9 @@ body {
|
|||||||
.card {
|
.card {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
max-width: 420px;
|
max-width: 500px;
|
||||||
width: 80%;
|
width: 85%;
|
||||||
height: 260px;
|
height: 300px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
flex: none;
|
flex: none;
|
||||||
perspective: 1000px;
|
perspective: 1000px;
|
||||||
@@ -52,10 +53,11 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
min-height: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
background: #111;
|
background: linear-gradient(145deg, #161616, #0e0e0e);
|
||||||
border-radius: 3px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card__figure::before {
|
.card__figure::before {
|
||||||
@@ -65,10 +67,10 @@ body {
|
|||||||
height: calc(100% - 14px);
|
height: calc(100% - 14px);
|
||||||
top: 6px;
|
top: 6px;
|
||||||
left: 6px;
|
left: 6px;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
border-radius: 2px;
|
border-radius: 3px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
box-shadow: 0 30px 20px rgb(0 0 0 / 50%);
|
box-shadow: 0 30px 40px rgb(0 0 0 / 60%), 0 0 80px rgb(0 0 0 / 30%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card__deco {
|
.card__deco {
|
||||||
@@ -98,7 +100,7 @@ body {
|
|||||||
|
|
||||||
figcaption {
|
figcaption {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 25px;
|
padding: 30px;
|
||||||
color: #eee;
|
color: #eee;
|
||||||
font-family: Avenir;
|
font-family: Avenir;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -108,9 +110,10 @@ figcaption {
|
|||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 3.3rem;
|
font-size: 3.6rem;
|
||||||
line-height: 3.3rem;
|
line-height: 3.6rem;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.links {
|
.links {
|
||||||
@@ -123,17 +126,17 @@ h1 {
|
|||||||
a {
|
a {
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
transition: color 100ms ease;
|
transition: color 100ms ease;
|
||||||
height: 20px;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 44px;
|
||||||
|
min-height: 44px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
color: #eee;
|
color: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
@@ -152,7 +155,14 @@ svg {
|
|||||||
|
|
||||||
@media screen and (max-width: 430px) {
|
@media screen and (max-width: 430px) {
|
||||||
.card {
|
.card {
|
||||||
height: 450px;
|
width: 85%;
|
||||||
|
height: auto;
|
||||||
|
aspect-ratio: 3 / 4;
|
||||||
|
max-height: 70dvh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links span {
|
||||||
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
<title>Travis Heinström</title>
|
<title>Travis Heinström</title>
|
||||||
@@ -51,9 +50,9 @@
|
|||||||
<script src="./js/main.js"></script>
|
<script src="./js/main.js"></script>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
new TiltFx(document.querySelector(".card", {}));
|
new TiltFx(document.querySelector(".card"), {});
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<script defer data-domain="heinstrom.com" src="https://plausible.nyc.h.rip/js/script.js"></script>
|
<script defer src="https://a.log.rip/script.js" data-website-id="1e9e073c-dd8c-4dfd-b939-5066f4239877"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
229
js/main.js
229
js/main.js
@@ -1,7 +1,6 @@
|
|||||||
(function (window) {
|
(function (window) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// Helper vars and functions.
|
|
||||||
function extend(a, b) {
|
function extend(a, b) {
|
||||||
for (var key in b) {
|
for (var key in b) {
|
||||||
if (b.hasOwnProperty(key)) {
|
if (b.hasOwnProperty(key)) {
|
||||||
@@ -11,26 +10,6 @@
|
|||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMousePos(e) {
|
|
||||||
var posx = 0,
|
|
||||||
posy = 0;
|
|
||||||
if (!e) var e = window.event;
|
|
||||||
if (e.pageX || e.pageY) {
|
|
||||||
posx = e.pageX;
|
|
||||||
posy = e.pageY;
|
|
||||||
} else if (e.clientX || e.clientY) {
|
|
||||||
posx =
|
|
||||||
e.clientX +
|
|
||||||
document.body.scrollLeft +
|
|
||||||
document.documentElement.scrollLeft;
|
|
||||||
posy =
|
|
||||||
e.clientY +
|
|
||||||
document.body.scrollTop +
|
|
||||||
document.documentElement.scrollTop;
|
|
||||||
}
|
|
||||||
return { x: posx, y: posy };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TiltFx obj.
|
* TiltFx obj.
|
||||||
*/
|
*/
|
||||||
@@ -73,7 +52,9 @@
|
|||||||
this.DOM.animatable.shine = this.DOM.el.querySelector(
|
this.DOM.animatable.shine = this.DOM.el.querySelector(
|
||||||
".card__deco--shine > div"
|
".card__deco--shine > div"
|
||||||
);
|
);
|
||||||
|
this._gyroActive = false;
|
||||||
this._initEvents();
|
this._initEvents();
|
||||||
|
this._initGyro();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,75 +70,68 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.mousemoveFn = function (ev) {
|
this.mousemoveFn = function (ev) {
|
||||||
|
if (self._gyroActive) return;
|
||||||
requestAnimationFrame(function () {
|
requestAnimationFrame(function () {
|
||||||
self._layout(ev);
|
var bounds = self.DOM.el.getBoundingClientRect();
|
||||||
|
self._applyTilt(
|
||||||
|
ev.clientX - bounds.left,
|
||||||
|
ev.clientY - bounds.top,
|
||||||
|
bounds.width,
|
||||||
|
bounds.height
|
||||||
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.mouseleaveFn = function (ev) {
|
this.mouseleaveFn = function () {
|
||||||
requestAnimationFrame(function () {
|
requestAnimationFrame(function () {
|
||||||
for (var key in self.DOM.animatable) {
|
self._animateReset();
|
||||||
if (self.options.movement[key] == undefined) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
anime({
|
|
||||||
targets: self.DOM.animatable[key],
|
|
||||||
duration:
|
|
||||||
self.options.movement[key].reverseAnimation != undefined
|
|
||||||
? self.options.movement[key].reverseAnimation.duration || 0
|
|
||||||
: 1,
|
|
||||||
easing:
|
|
||||||
self.options.movement[key].reverseAnimation != undefined
|
|
||||||
? self.options.movement[key].reverseAnimation.easing || "linear"
|
|
||||||
: "linear",
|
|
||||||
elasticity:
|
|
||||||
self.options.movement[key].reverseAnimation != undefined
|
|
||||||
? self.options.movement[key].reverseAnimation.elasticity || null
|
|
||||||
: null,
|
|
||||||
scaleX: 1,
|
|
||||||
scaleY: 1,
|
|
||||||
scaleZ: 1,
|
|
||||||
translateX: 0,
|
|
||||||
translateY: 0,
|
|
||||||
translateZ: 0,
|
|
||||||
rotateX: 0,
|
|
||||||
rotateY: 0,
|
|
||||||
rotateZ: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.debounceLeaveFn = function (e) {
|
this.debounceLeaveFn = function (e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
clearTimeout(self.timeout);
|
clearTimeout(self.timeout);
|
||||||
self.timeout = setTimeout(() => {
|
self.timeout = setTimeout(function () {
|
||||||
self.mouseleaveFn();
|
self._animateReset();
|
||||||
}, 25);
|
}, 25);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Touch handlers
|
||||||
|
this.touchstartFn = function () {
|
||||||
|
if (self._gyroActive) return;
|
||||||
|
for (var key in self.DOM.animatable) {
|
||||||
|
anime.remove(self.DOM.animatable[key]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.touchmoveFn = function (ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
if (self._gyroActive) return;
|
||||||
|
var touch = ev.touches[0];
|
||||||
|
if (!touch) return;
|
||||||
|
requestAnimationFrame(function () {
|
||||||
|
var bounds = self.DOM.el.getBoundingClientRect();
|
||||||
|
self._applyTilt(
|
||||||
|
touch.clientX - bounds.left,
|
||||||
|
touch.clientY - bounds.top,
|
||||||
|
bounds.width,
|
||||||
|
bounds.height
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
this.DOM.el.addEventListener("mousemove", this.mousemoveFn);
|
this.DOM.el.addEventListener("mousemove", this.mousemoveFn);
|
||||||
this.DOM.el.addEventListener("mouseleave", this.mouseleaveFn);
|
this.DOM.el.addEventListener("mouseleave", this.mouseleaveFn);
|
||||||
this.DOM.el.addEventListener("mouseenter", this.mouseenterFn);
|
this.DOM.el.addEventListener("mouseenter", this.mouseenterFn);
|
||||||
|
this.DOM.el.addEventListener("touchstart", this.touchstartFn, { passive: true });
|
||||||
|
this.DOM.el.addEventListener("touchmove", this.touchmoveFn, { passive: false });
|
||||||
this.DOM.el.addEventListener("touchend", this.debounceLeaveFn);
|
this.DOM.el.addEventListener("touchend", this.debounceLeaveFn);
|
||||||
};
|
};
|
||||||
|
|
||||||
TiltFx.prototype._layout = function (ev) {
|
/**
|
||||||
// Mouse position relative to the document.
|
* Apply tilt from a relative position within the card.
|
||||||
var mousepos = getMousePos(ev),
|
*/
|
||||||
// Document scrolls.
|
TiltFx.prototype._applyTilt = function (relX, relY, width, height) {
|
||||||
docScrolls = {
|
|
||||||
left: document.body.scrollLeft + document.documentElement.scrollLeft,
|
|
||||||
top: document.body.scrollTop + document.documentElement.scrollTop,
|
|
||||||
},
|
|
||||||
bounds = this.DOM.el.getBoundingClientRect(),
|
|
||||||
// Mouse position relative to the main element (this.DOM.el).
|
|
||||||
relmousepos = {
|
|
||||||
x: mousepos.x - bounds.left - docScrolls.left,
|
|
||||||
y: mousepos.y - bounds.top - docScrolls.top,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Movement settings for the animatable elements.
|
|
||||||
for (var key in this.DOM.animatable) {
|
for (var key in this.DOM.animatable) {
|
||||||
if (
|
if (
|
||||||
this.DOM.animatable[key] == undefined ||
|
this.DOM.animatable[key] == undefined ||
|
||||||
@@ -166,13 +140,9 @@
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var t =
|
var t =
|
||||||
this.options.movement[key] != undefined
|
this.options.movement[key].translation || { x: 0, y: 0, z: 0 },
|
||||||
? this.options.movement[key].translation || { x: 0, y: 0, z: 0 }
|
|
||||||
: { x: 0, y: 0, z: 0 },
|
|
||||||
r =
|
r =
|
||||||
this.options.movement[key] != undefined
|
this.options.movement[key].rotation || { x: 0, y: 0, z: 0 },
|
||||||
? this.options.movement[key].rotation || { x: 0, y: 0, z: 0 }
|
|
||||||
: { x: 0, y: 0, z: 0 },
|
|
||||||
setRange = function (obj) {
|
setRange = function (obj) {
|
||||||
for (var k in obj) {
|
for (var k in obj) {
|
||||||
if (obj[k] == undefined) {
|
if (obj[k] == undefined) {
|
||||||
@@ -188,20 +158,18 @@
|
|||||||
|
|
||||||
var transforms = {
|
var transforms = {
|
||||||
translation: {
|
translation: {
|
||||||
x: ((t.x[1] - t.x[0]) / bounds.width) * relmousepos.x + t.x[0],
|
x: ((t.x[1] - t.x[0]) / width) * relX + t.x[0],
|
||||||
y: ((t.y[1] - t.y[0]) / bounds.height) * relmousepos.y + t.y[0],
|
y: ((t.y[1] - t.y[0]) / height) * relY + t.y[0],
|
||||||
z: ((t.z[1] - t.z[0]) / bounds.height) * relmousepos.y + t.z[0],
|
z: ((t.z[1] - t.z[0]) / height) * relY + t.z[0],
|
||||||
},
|
},
|
||||||
rotation: {
|
rotation: {
|
||||||
x: ((r.x[1] - r.x[0]) / bounds.height) * relmousepos.y + r.x[0],
|
x: ((r.x[1] - r.x[0]) / height) * relY + r.x[0],
|
||||||
y: ((r.y[1] - r.y[0]) / bounds.width) * relmousepos.x + r.y[0],
|
y: ((r.y[1] - r.y[0]) / width) * relX + r.y[0],
|
||||||
z: ((r.z[1] - r.z[0]) / bounds.width) * relmousepos.x + r.z[0],
|
z: ((r.z[1] - r.z[0]) / width) * relX + r.z[0],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
this.DOM.animatable[key].style.WebkitTransform = this.DOM.animatable[
|
this.DOM.animatable[key].style.transform =
|
||||||
key
|
|
||||||
].style.transform =
|
|
||||||
"translateX(" +
|
"translateX(" +
|
||||||
transforms.translation.x +
|
transforms.translation.x +
|
||||||
"px) translateY(" +
|
"px) translateY(" +
|
||||||
@@ -218,5 +186,96 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animate back to resting position.
|
||||||
|
*/
|
||||||
|
TiltFx.prototype._animateReset = function () {
|
||||||
|
for (var key in this.DOM.animatable) {
|
||||||
|
if (this.options.movement[key] == undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
anime({
|
||||||
|
targets: this.DOM.animatable[key],
|
||||||
|
duration:
|
||||||
|
this.options.movement[key].reverseAnimation != undefined
|
||||||
|
? this.options.movement[key].reverseAnimation.duration || 0
|
||||||
|
: 1,
|
||||||
|
easing:
|
||||||
|
this.options.movement[key].reverseAnimation != undefined
|
||||||
|
? this.options.movement[key].reverseAnimation.easing || "linear"
|
||||||
|
: "linear",
|
||||||
|
elasticity:
|
||||||
|
this.options.movement[key].reverseAnimation != undefined
|
||||||
|
? this.options.movement[key].reverseAnimation.elasticity || null
|
||||||
|
: null,
|
||||||
|
scaleX: 1,
|
||||||
|
scaleY: 1,
|
||||||
|
scaleZ: 1,
|
||||||
|
translateX: 0,
|
||||||
|
translateY: 0,
|
||||||
|
translateZ: 0,
|
||||||
|
rotateX: 0,
|
||||||
|
rotateY: 0,
|
||||||
|
rotateZ: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init gyroscope / device orientation tilt.
|
||||||
|
* On iOS 13+ this requires a user gesture to request permission.
|
||||||
|
*/
|
||||||
|
TiltFx.prototype._initGyro = function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!window.DeviceOrientationEvent) return;
|
||||||
|
|
||||||
|
// iOS 13+ requires explicit permission request — skip those devices
|
||||||
|
if (typeof DeviceOrientationEvent.requestPermission === "function") return;
|
||||||
|
|
||||||
|
// On Android and other devices, test if events actually fire
|
||||||
|
var testHandler = function (e) {
|
||||||
|
if (e.gamma !== null && e.beta !== null) {
|
||||||
|
self._bindGyro();
|
||||||
|
}
|
||||||
|
window.removeEventListener("deviceorientation", testHandler);
|
||||||
|
};
|
||||||
|
window.addEventListener("deviceorientation", testHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
TiltFx.prototype._bindGyro = function () {
|
||||||
|
var self = this;
|
||||||
|
this._gyroActive = true;
|
||||||
|
|
||||||
|
// Stop any in-progress touch/mouse animations
|
||||||
|
for (var key in this.DOM.animatable) {
|
||||||
|
anime.remove(this.DOM.animatable[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("deviceorientation", function (e) {
|
||||||
|
requestAnimationFrame(function () {
|
||||||
|
// beta: front-back tilt (-180 to 180), gamma: left-right tilt (-90 to 90)
|
||||||
|
var beta = e.beta || 0;
|
||||||
|
var gamma = e.gamma || 0;
|
||||||
|
|
||||||
|
// Clamp to reasonable range
|
||||||
|
beta = Math.max(-30, Math.min(30, beta));
|
||||||
|
gamma = Math.max(-30, Math.min(30, gamma));
|
||||||
|
|
||||||
|
// Normalize to 0-1 range (centered at 0 degrees = 0.5)
|
||||||
|
var normX = (gamma + 30) / 60;
|
||||||
|
var normY = (beta + 30) / 60;
|
||||||
|
|
||||||
|
var bounds = self.DOM.el.getBoundingClientRect();
|
||||||
|
self._applyTilt(
|
||||||
|
normX * bounds.width,
|
||||||
|
normY * bounds.height,
|
||||||
|
bounds.width,
|
||||||
|
bounds.height
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
window.TiltFx = TiltFx;
|
window.TiltFx = TiltFx;
|
||||||
})(window);
|
})(window);
|
||||||
|
|||||||
Reference in New Issue
Block a user