Matt Morris

How to create a digital coaster

Authors
  • avatar
    Name
    Matt Morris
    LinkedIn

Aside from the collection itself, which you can view here, I wanted to make a separate post covering the technologies and techniques used to create it.

The idea was to create a digital approximation of holding a coaster in your hand, with the ability to flip it over and see the other side in a natural feeling way, instead of simply changing the image abruptly on-click.

It was an early web development project of mine that has existed in some form for several years before I finally published it here, and transformed it along the way from simple HTML/CSS with JQuery, to various React packages, and finally to Framer Motion which I currently use to create the flip animation.

The images

To start, I took front and back pictures of each coaster.

Using digital editing tools (first Photoshop, later Figma) I created a shape to use as a mask for cropping the image out of its background.

The code

The top layer of the code is JSX (which stands for Javascript XML), React's way to allow users to write HTML code in a relatively natural way that works within the React library. This is held in an MDX file (the markdown file format used within React).

JSX/HTML

<div className="coaster-container">
  <div className="sub-coaster-container hoverWrapper">
    <FlippingCard
      className="coaster"
      frontImage="/static/images/coasters/balefront.png"
      backImage="/static/images/coasters/baleback.png"
    />
    <div className="hoverShow">
      <div>Bale Breaker Brewing Company</div>
      <div>Yakima, Washington</div>
    </div>
  </div>
</div>

CSS

CSS for the positioning, text styling, and the animated company title/location block that appears underneath the coasters on-hover:


/* Default coaster styles */

.coaster-container {
  text-align: center;
  margin-top: 30px;
  vertical-align: top;
}

.sub-coaster-container {
  text-align: center;
  display: inline-block;
  margin-right: 50px;
  vertical-align: top;
}

@media (max-width: 767px) {
  .sub-coaster-container {
    margin-right: 20px;
  }
}

.hoverWrapper:hover .hoverShow {
  opacity: 1;
  visibility: visible;
  margin-bottom: 50px;
}

.hoverWrapper .hoverShow {
  opacity: 0;
  visibility: hidden;
  text-align: center;
  line-height: 16pt;
  font-size: 9pt;
  font-family: sans-serif;
  margin-top: 50px;
  margin-bottom: 0;
  transition:
    margin-bottom 0.3s ease,
    opacity 0.2s ease,
    visibility 0.3s ease;
}

React component (TypeScript)

The project uses a React component to import the Framer Motion library and program the 'flip' effect on the edited coaster images. If you refer back to the JSX/HTML code above, you can see where the FlippingCard component for each coaster is pulled in.


'use client'

import React, { useState } from 'react'
import { motion } from 'framer-motion'
import Image from 'next/image'

const FlippingCard = ({ frontImage, backImage, width = 200, height = 200 }) => {
  const [isFlipped, setIsFlipped] = useState(false)

  const handleClick = () => {
    setIsFlipped(!isFlipped)
  }

  const handleKeyDown = (event) => {
    if (event.key === 'Enter' || event.key === ' ') {
      setIsFlipped(!isFlipped)
    }
  }

  return (
    <div
      onClick={handleClick}
      onKeyDown={handleKeyDown}
      role="button"
      tabIndex={0}
      style={{
        perspective: '1000px',
        position: 'relative',
        width: `${width}px`,
        height: `${height}px`,
      }}
    >
      <motion.div
        style={{
          width: '100%',
          height: '100%',
          position: 'absolute',
          transformStyle: 'preserve-3d',
          transition: 'transform 0.6s',
          transform: isFlipped ? 'rotateY(180deg)' : 'rotateY(0deg)',
        }}
      >
        <div
          style={{
            width: '100%',
            height: '100%',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            backfaceVisibility: 'hidden',
            position: 'absolute',
            opacity: isFlipped ? 0 : 1,
            transition: 'opacity 0.6s',
          }}
        >
          <Image src={frontImage} alt="Front" fill className="object-cover" />
        </div>
        <div
          style={{
            width: '100%',
            height: '100%',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            backfaceVisibility: 'hidden',
            position: 'absolute',
            transform: 'rotateY(180deg)',
            opacity: isFlipped ? 1 : 0,
            transition: 'opacity 0.6s',
          }}
        >
          <Image src={backImage} alt="Back" fill className="object-cover" />
        </div>
      </motion.div>
    </div>
  )
}

export default FlippingCard

The result:

Voilà, a digital coaster. See the full collection here

Front
Back
Aslan Brewing Co.
Bellingham, Washington