Created
January 13, 2021 10:42
-
-
Save pete-rai/587c51877aa7ceb863a90c03a9968723 to your computer and use it in GitHub Desktop.
A 3D wheel of cards where cards can be added and removed and the wheel spun to place any card at the front. This is a pure CSS solution based on transforms. Modify the card template to make whatever you want to appear inside each card. See it in action here: https://codepen.io/pete-rai/full/dypgwLw
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no"> | |
<title>3D Card Wheel</title> | |
<style> | |
html, | |
body { | |
font-family: sans-serif; | |
} | |
#container { | |
left: 50%; | |
margin: -1% auto; | |
perspective: 1000px; | |
position: fixed; | |
top: 40%; | |
transform: translate(-50%, -50%); | |
width: 350px; | |
} | |
#template { | |
display: none; | |
} | |
.wheel { | |
height: 100%; | |
position: absolute; | |
transform-style: preserve-3d; | |
transition: transform 1s; | |
width: 100%; | |
} | |
.wheel .card { | |
background: lightgrey; | |
border-radius: 10px; | |
border: 1px solid grey; | |
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1), 0 3px 6px rgba(0, 0, 0, 0.2); | |
color: #666; | |
font-size: 3rem; | |
height: 200px; | |
line-height: 6rem; | |
position: absolute; | |
text-align: center; | |
transition: transform 1s, opacity 1s; | |
width: 300px; | |
} | |
.wheel .card.current { | |
background: darkgrey; | |
color: #ddd; | |
} | |
#control { | |
bottom: 0; | |
left: 50%; | |
margin: 25px; | |
position: fixed; | |
transform: translateX(-50%); | |
width: 700px; | |
} | |
#control button { | |
font-size: 1.4rem; | |
} | |
#control button.disabled { | |
color: lightgrey; | |
} | |
#control span { | |
font-size: 0.8rem; | |
} | |
#control input[type="text"] { | |
border: 1px solid grey; | |
font-size: 1.2rem; | |
padding: 2px; | |
text-align: center; | |
} | |
#control input[type="range"] { | |
vertical-align: middle; | |
} | |
table#info { | |
border-collapse: collapse; | |
margin: 15px; | |
position: fixed; | |
right: 0; | |
top: 0; | |
} | |
table#info tr td, | |
table#info tr th { | |
border: 1px solid grey; | |
padding: 8px; | |
} | |
table#info tr th { | |
background: lightgrey; | |
font-weight: normal; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="template" class="card"></div> | |
<div id="container"> | |
<div class="wheel"></div> | |
</div> | |
<div id="control"> | |
<span>angle</span> | |
<input id="angle" type="range" min="-90" max="+90" value="0" step="1"/> | |
<button id="del">-</button> | |
<button id="prev">«</button> | |
<input id="curr" type="text" size="3"/> | |
<button id="next">»</button> | |
<button id="add">+</button> | |
<input id="speed" type="range" min="0.5" max="3.0" value="0" step="0.25"/> | |
<span>speed</span> | |
</div> | |
<table id="info"> | |
<tr><th>angle</th><td id="_angle"></td></tr> | |
<tr><th>showing</th><td id="_showing"></td></tr> | |
<tr><th>current</th><td id="_current"></td></tr> | |
<tr><th>speed</th><td id="_speed"></td></tr> | |
</table> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.0/jquery.min.js"></script> | |
<script type="text/javascript"> | |
// --- default values | |
var angle = 10; // degrees | |
var showing = 12; // cards | |
var max = 16; // cards | |
var speed = 1; // seconds | |
// --- global variables | |
var current = 0; | |
var radius = 0; | |
var theta = 0; | |
var timer = null; | |
// --- generates the card id | |
function id(idx, count = showing) { | |
idx %= count; | |
return (idx < 0 ? idx + count : idx).toString().padStart(2, "0"); | |
} | |
// --- rotate the card wheel | |
function rotate() { | |
$(".wheel").css( | |
"transform", | |
`translateZ(${-radius}px) rotateX(${-angle}deg) rotateY(${ | |
-theta * current | |
}deg)` | |
); | |
$("#curr").val(current); | |
$(".card").removeClass("current"); | |
clearTimeout(timer); | |
timer = setTimeout(function () { | |
$(`#card_${id(current)}`).addClass("current"); | |
}, 0.9 * (speed * 1000)); // a tad before the animation ends | |
info(); | |
} | |
// --- handlers | |
$("#prev").click(function () { | |
current--; | |
rotate(); | |
}); | |
$("#next").click(function () { | |
current++; | |
rotate(); | |
}); | |
$("#curr").change(function () { | |
current = Math.min(showing - 1, Math.max(0, parseInt($(this).val()))); | |
rotate(); | |
}); | |
$("#angle").change(function () { | |
angle = $(this).val(); | |
rotate(); | |
}); | |
$("#speed").change(function () { | |
speed = $(this).val(); | |
change(); | |
}); | |
$("#add").click(function () { | |
showing = Math.min(showing + 1, max); | |
change(); | |
}); | |
$("#del").click(function () { | |
showing = Math.max(showing - 1, 0); | |
change(); | |
}); | |
// --- show the info | |
function info() { | |
let info = $("#info"); | |
$("#_angle").text(`${angle} deg`); | |
$("#_showing").text(`${showing} of ${max}`); | |
$("#_current").text(`card ${id(current)}`); | |
$("#_speed").text(`${speed} secs`); | |
} | |
// --- process a change | |
function change() { | |
width = $(".wheel").outerWidth(); | |
theta = showing ? 360 / showing : 1; | |
radius = Math.max(100, Math.round(width / 2 / Math.tan(Math.PI / showing))); // 100 is an arbitary minimum width | |
$(".wheel .card").each(function (idx) { | |
if (idx < showing) { | |
$(this) | |
.css("opacity", "1") | |
.css("transform", `rotateY(${theta * idx}deg) translateZ(${radius}px)`); | |
} else { | |
$(this).css("opacity", "0").css("transform", "none"); | |
} | |
}); | |
$(".wheel, .wheel .card").css("transition-duration", `${speed}s`); | |
$("#add").toggleClass("disabled", showing == max); | |
$("#del").toggleClass("disabled", showing == 0); | |
rotate(); | |
} | |
// --- initial setup | |
function setup() { | |
for (let i = 0; i < max; i++) { | |
let name = id(i, max); | |
let card = $("#template") | |
.clone() | |
.css("opacity", "0") | |
.attr("id", `card_${name}`) | |
.text(name); | |
$(".wheel").append(card); | |
} | |
$("#angle").val(angle); | |
$("#speed").val(speed); | |
change(); | |
} | |
setup(); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment