diff --git a/css/style.css b/css/style.css
index e76cf26..d65b758 100755
--- a/css/style.css
+++ b/css/style.css
@@ -4,7 +4,9 @@
html, body {
height: 100%;
+ height: 100dvh;
min-height: 100%;
+ min-height: 100dvh;
overflow: hidden;
margin: 0;
padding: 0;
@@ -12,7 +14,6 @@ html, body {
body {
background-color: #0e0e0e;
- margin: 0;
display: flex;
justify-content: center;
align-items: center;
@@ -52,6 +53,7 @@ body {
margin: 0;
width: 100%;
height: 100%;
+ min-height: 100%;
display: block;
position: relative;
background: #111;
@@ -123,17 +125,17 @@ h1 {
a {
color: #ddd;
transition: color 100ms ease;
- height: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 44px;
+ min-height: 44px;
}
a:hover {
color: #eee;
}
-p {
- margin: 0;
-}
-
svg {
width: 20px;
height: 20px;
@@ -152,7 +154,14 @@ svg {
@media screen and (max-width: 430px) {
.card {
- height: 450px;
+ width: 85%;
+ height: auto;
+ aspect-ratio: 3 / 4;
+ max-height: 70dvh;
+ }
+
+ .links span {
+ font-size: 0.85rem;
}
}
diff --git a/index.html b/index.html
index 87bca54..42c78b8 100755
--- a/index.html
+++ b/index.html
@@ -2,7 +2,6 @@
-
Travis Heinström
@@ -51,7 +50,7 @@
diff --git a/js/main.js b/js/main.js
index f292116..af229bb 100644
--- a/js/main.js
+++ b/js/main.js
@@ -1,7 +1,6 @@
(function (window) {
"use strict";
- // Helper vars and functions.
function extend(a, b) {
for (var key in b) {
if (b.hasOwnProperty(key)) {
@@ -11,26 +10,6 @@
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.
*/
@@ -73,7 +52,9 @@
this.DOM.animatable.shine = this.DOM.el.querySelector(
".card__deco--shine > div"
);
+ this._gyroActive = false;
this._initEvents();
+ this._initGyro();
};
/**
@@ -89,75 +70,67 @@
};
this.mousemoveFn = function (ev) {
+ if (self._gyroActive) return;
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 () {
- for (var key in self.DOM.animatable) {
- 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,
- });
- }
+ self._animateReset();
});
};
this.debounceLeaveFn = function (e) {
e.stopPropagation();
clearTimeout(self.timeout);
- self.timeout = setTimeout(() => {
- self.mouseleaveFn();
+ self.timeout = setTimeout(function () {
+ self._animateReset();
}, 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) {
+ 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("mouseleave", this.mouseleaveFn);
this.DOM.el.addEventListener("mouseenter", this.mouseenterFn);
+ this.DOM.el.addEventListener("touchstart", this.touchstartFn, { passive: true });
+ this.DOM.el.addEventListener("touchmove", this.touchmoveFn, { passive: true });
this.DOM.el.addEventListener("touchend", this.debounceLeaveFn);
};
- TiltFx.prototype._layout = function (ev) {
- // Mouse position relative to the document.
- var mousepos = getMousePos(ev),
- // Document scrolls.
- 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.
+ /**
+ * Apply tilt from a relative position within the card.
+ */
+ TiltFx.prototype._applyTilt = function (relX, relY, width, height) {
for (var key in this.DOM.animatable) {
if (
this.DOM.animatable[key] == undefined ||
@@ -166,13 +139,9 @@
continue;
}
var t =
- this.options.movement[key] != undefined
- ? this.options.movement[key].translation || { x: 0, y: 0, z: 0 }
- : { x: 0, y: 0, z: 0 },
+ this.options.movement[key].translation || { x: 0, y: 0, z: 0 },
r =
- this.options.movement[key] != undefined
- ? this.options.movement[key].rotation || { x: 0, y: 0, z: 0 }
- : { x: 0, y: 0, z: 0 },
+ this.options.movement[key].rotation || { x: 0, y: 0, z: 0 },
setRange = function (obj) {
for (var k in obj) {
if (obj[k] == undefined) {
@@ -188,20 +157,18 @@
var transforms = {
translation: {
- x: ((t.x[1] - t.x[0]) / bounds.width) * relmousepos.x + t.x[0],
- y: ((t.y[1] - t.y[0]) / bounds.height) * relmousepos.y + t.y[0],
- z: ((t.z[1] - t.z[0]) / bounds.height) * relmousepos.y + t.z[0],
+ x: ((t.x[1] - t.x[0]) / width) * relX + t.x[0],
+ y: ((t.y[1] - t.y[0]) / height) * relY + t.y[0],
+ z: ((t.z[1] - t.z[0]) / height) * relY + t.z[0],
},
rotation: {
- x: ((r.x[1] - r.x[0]) / bounds.height) * relmousepos.y + r.x[0],
- y: ((r.y[1] - r.y[0]) / bounds.width) * relmousepos.x + r.y[0],
- z: ((r.z[1] - r.z[0]) / bounds.width) * relmousepos.x + r.z[0],
+ x: ((r.x[1] - r.x[0]) / height) * relY + r.x[0],
+ y: ((r.y[1] - r.y[0]) / width) * relX + r.y[0],
+ z: ((r.z[1] - r.z[0]) / width) * relX + r.z[0],
},
};
- this.DOM.animatable[key].style.WebkitTransform = this.DOM.animatable[
- key
- ].style.transform =
+ this.DOM.animatable[key].style.transform =
"translateX(" +
transforms.translation.x +
"px) translateY(" +
@@ -218,5 +185,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);