Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HCKgit/16974cfbf47f9d5f622869b4c04c177d to your computer and use it in GitHub Desktop.
Save HCKgit/16974cfbf47f9d5f622869b4c04c177d to your computer and use it in GitHub Desktop.
Hearthstone Card CSS 3D Click/Drag

Hearthstone Card CSS 3D Click/Drag

In honor of the release of the Whispers of the Old Gods expansion for Hearthstone, I wanted to share this proof of concept. The card drag feature in the game has always been one of my favorite things to play with. It feels like the card is reacting to air resistance as you drag it around.

Don't read in to the code too much, as it could be optimized quite a bit. This pen was more just to explore the concept of the 3D click and drag. Let me know if you have any questions, and please fork if you'd like to play around or enhance it!

PS - The audio is a little finicky, just wanted something rough for the effect.

A Pen by Jack Rugile on CodePen.

License.

<div class="card">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/836/hearthstone-ragnaros.png" class="card-image" draggable="false">
</div>
var card,
image,
cardw,
cardh,
cardx,
cardy,
ocardx,
ocardy,
pinx,
piny,
pinxperc,
pinyperc,
targetx,
targety,
rx,
ry,
targetrx,
targetry,
scale,
targetscale,
ww,
wh,
md,
mx,
my,
whoosh,
whooshvol,
whooshvoltarget,
majesty,
majestyvol,
majestyvoltarget,
audioloaded;
function audioload() {
audioloaded++;
if( audioloaded == 2 ) {
document.body.classList.add( 'loaded' );
majesty.play();
whoosh.play();
bindevents();
loop();
}
}
function init() {
onresize();
card = document.querySelector( '.card' );
image = document.querySelector( '.card-image' );
cardw = image.width;
cardh = image.height;
cardx = ww / 2 - cardw / 2;
cardy = wh / 2 - cardh / 2;
ocardx = cardx;
ocardy = cardy;
pinx = 0;
piny = 0;
pinxperc = 0;
pinyperc = 0;
targetx = cardx;
targety = cardy;
rx = 0;
ry = 0;
targetrx = 0;
targetry = 0;
scale = 1;
targetscale = scale;
md = false;
mx = cardx;
my = cardy;
audioloaded = 0;
whooshvol = 0;
whooshvoltarget = 0;
whoosh = new Audio();
whoosh.addEventListener( 'canplaythrough', audioload );
whoosh.src = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/836/hs-whoosh.ogg';
whoosh.volume = 0;
whoosh.loop = true;
majestyvol = 0;
majestyvoltarget = 0;
majesty = new Audio();
majesty.addEventListener( 'canplaythrough', audioload );
majesty.src = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/836/hs-majesty.ogg';
majesty.volume = 0;
majesty.loop = true;
}
function bindevents() {
card.addEventListener( 'mousedown', onmousedown );
window.addEventListener( 'mouseup', onmouseup );
window.addEventListener( 'mousemove', onmousemove );
window.addEventListener( 'resize', onresize );
}
function onmousedown( e ) {
md = true;
mx = e.pageX;
my = e.pageY;
pinx = cardw / 2; // pin to center
piny = cardh / 2; // pin to center
//pinx = mx - cardx; // pin to click point
//piny = my - cardy; // pin to click point
pinxperc = 100 - ( pinx / cardw ) * 100; // transform based on the pin position
pinyperc = 100 - ( piny / cardh ) * 100; // transform based on the pin position
}
function onmouseup() {
md = false;
}
function onmousemove( e ) {
if( md ) {
mx = e.pageX;
my = e.pageY;
}
}
function onresize() {
ww = window.innerWidth;
wh = window.innerHeight;
}
function loop() {
requestAnimationFrame( loop )
// set new target position
targetx = mx - cardx - pinx;
targety = my - cardy - piny;
// lerp to new position
cardx += targetx * 0.25;
cardy += targety * 0.25;
// contain card to window bounds
if( cardx < -cardw / 2 ) {
cardx = -cardw / 2;
}
if( cardx > ww - cardw / 2 ) {
cardx = ww - cardw / 2;
}
if( cardy < -cardh / 2 ) {
cardy = -cardh / 2;
}
if( cardy > wh - cardh / 2 ) {
cardy = wh - cardh / 2;
}
// get rotation based on how much card moved
targetrx = ( ocardy - cardy - rx ) * 3;
targetry = ( cardx - ocardx - ry ) * 3;
// lock rotation so things don't get too crazy
targetrx = Math.min( targetrx, 90 );
targetrx = Math.max( targetrx, -90 );
targetry = Math.min( targetry, 90 );
targetry = Math.max( targetry, -90 );
// lerp to new rotation
rx += targetrx * 0.1;
ry += targetry * 0.1;
// scale up when the mouse is pressed
targetscale = md ? 1.2 - scale : 1 - scale;
scale += targetscale * 0.2;
// apply the transform
card.style[ 'transform' ] = 'translate3d(' + cardx + 'px, ' + cardy + 'px, 0)';
image.style[ 'transform-origin' ] = pinxperc + '% ' + pinyperc + '%';
image.style[ 'transform' ] = 'scale(' + scale + ') rotateY(' + ry + 'deg) rotateX(' + rx + 'deg)';
majestyvoltarget = md ? 0.2 : 0;
majestyvol += ( majestyvoltarget - majestyvol ) * 0.1;
majesty.volume = majestyvol;
whooshvoltarget = ( Math.abs( ( ocardy - cardy ) ) + Math.abs( ( ocardx - cardx ) ) ) * 0.003;
whooshvol += ( whooshvoltarget - whooshvol ) * 0.1;
whoosh.volume = Math.min( whooshvol, 1 );
// store the old card position
ocardx = cardx;
ocardy = cardy;
}
// pull up a chair by the hearth!
window.onload = init;
html,
body {
height: 100%;
}
body {
background: #000;
cursor: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/836/hearthstone-cursor.png) 10 2, auto;
overflow: hidden;
&:active {
cursor: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/836/hearthstone-click.png) 10 2, auto;
}
&:after {
background-color: #000;
background-image: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/836/hearthstone-battlefield.jpg);
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
bottom: 0;
content: '';
filter: blur( 8px );
left: 0;
opacity: 0.8;
position: absolute;
right: 0;
top: 0;
will-change: transform;
z-index: -1;
}
}
.card {
left: 0;
top: 0;
perspective: 400px;
position: absolute;
transform-style: preserve-3d;
will-change: transform;
width: 250px;
user-drag: none;
user-select: none;
-moz-user-select: none;
-webkit-user-drag: none;
-webkit-user-select: none;
-ms-user-select: none;
&:active {
cursor: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/836/hearthstone-grab.png) 10 2, auto;
}
}
.card-image {
display: block;
pointer-events: none;
transform-style: preserve-3d;
width: 100%;
will-change: transform;
user-drag: none;
user-select: none;
-moz-user-select: none;
-webkit-user-drag: none;
-webkit-user-select: none;
-ms-user-select: none;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment