Steam-like Card Interaction by Vanilla JS and CSS
Tags: javascript, beginners
Published: 2021-12-15 04:02:00

Steam trading cards are randomly collectible cards during gameplay. It's not a pleasure to have all cards collected since players have to exchange their cards with others (or buy them), but interacting with cards on the game page can be interesting.

![steam trading card interaction](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bt7r5nejzv8b7ng3aune.gif)

I made a similar interaction with a few tweaks.

![preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/im0l5iin8je39sxcarcx.gif)

Four parts of the trading card are changing — *size*, *perspective*, *filter*, and *shadow*.

## Perspective

To reproduce the effect, the powerful [CSS 3D transform](https://www.w3.org/TR/css-transforms-1/) feature has to be involved. The dynamic perspective of the card will imply hovering and moving actions.

There are two ways to activate 3D mode for an HTML element, by [`perspective`](https://developer.mozilla.org/en-US/docs/Web/CSS/perspective) or [`transform: perspective()`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/transform-function/perspective()) in CSS. The former one is used by the parent element, which creates a 3D space for its child elements, while the latter one is for a single element. David DeSandro has [a great series](https://3dtransforms.desandro.com/) explaining CSS 3D, go check that if you want to go down the rabbit hole.

* `transform: perspective(400px) rotateY(45deg)`

  {% codepen https://codepen.io/desandro/pen/WJpmdO default-tab=css,result %}

* `perspective: 400px`

  {% codepen https://codepen.io/desandro/pen/MGpxVG default-tab=css,result %}

## Rotation

As their names suggest, `rotateX()`, `rotateY()`, and `rotateZ()` can be used to rotate an element in 3D space. If perspective is not enabled, rotate functions will only perform a parallel projection, which is not the case we desire.


![rotate function](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tqm9eig7f98jsffkyarq.png)

The rotation degree adjusts proportionally to cursor position and [`mousemove`](https://developer.mozilla.org/en-US/docs/Web/API/Element/mousemove_event),  which is fired at an element when a mouse is moved while the cursor's hotspot is inside it, is the event to detect that. It is different from [`mouseenter`](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/mouseenter_event), which indicates the moment the cursor moves in an element.

```javascript
card.addEventListener("mousemove", function (event) {
  card.style.transform = `perspective(1000px)
  rotateY(${rotate(event.x, centerX)}deg)
  rotateX(${-rotate(event.y, centerY)}deg)`;
})
```

The rotation degree can be calculated by the relative position of the cursor and card center. A threshold is needed here to avoid a large rotation degree, in which case the card may turn very thin and impossible to hover above.

```javascript
function rotate(cursorPosition, centerPosition, threshold = 20) {
  if (cursorPosition - centerPosition >= 0) {
    return (cursorPosition - centerPosition) >= threshold ? threshold : (cursorPosition - centerPosition);
  } else {
    return (cursorPosition - centerPosition) <= -threshold ? -threshold : (cursorPosition - centerPosition);
  }
}
```

## Brightness

As you slide up and down on the card, you will notice a change in the reflection effect. This is not some kind of gradient color overlay effect, but a [`brightness`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter-function/brightness()) filter.

```javascript
function brightness(cursorPositionY, centerPositionY, strength = 50) {
  return 1 - rotate(cursorPositionY, centerPositionY)/strength;
}
// ...
card.style.filter = `brightness(${brightness(event.y, centerY)})`;
```

## Shadow

The shadow is a key part of this interaction and is oriented in the opposite direction of the cursor. I applied a large `blur-radius` here as Steam does.

```jsx
card.style.boxShadow = `${-rotate(event.x, centerX)}px ${-rotate(event.y, centerY)}px 80px 0px rgba(48, 65, 0, 0.5)`;
```

Add `transition` in CSS to make everything feels smooth.

```css
#card {
	transition: .2s;
}
```

When everything is finished, make sure the card restores to the initial look after the cursor leaves the area.

```css
card.addEventListener("mouseleave", function (event) {
  card.style.transform = `perspective(500px)`;
  card.style.width = `120px`;
  card.style.height = `160px`;
  card.style.filter = `brightness(1)`;
  card.style.boxShadow = `0 0 0 0 rgba(48, 65, 0, 0.5)`;
})
```

Thank you for reading this. You can view the result below:

1. Source Code: https://github.com/imchell/steam-like-card-curation
2. Preview (open with desktop browser): [https://steam-like-card-curation.vercel.app/](https://steam-like-card-curation.vercel.app/)