Victorian fox looking at a computer.
Jack of all trad.es

a work in progress - dev & misc

CSS Book Generator Re-hash

When I found Sebastien Castiel's online book generator years ago, I was thoroughly impressed. Having self-published two books, this is the type of thing I knew I'd want to use if I ever went and made a proper authorial website. Sebastien has a blog post about his initial creation, which I am shamelessly hacking.

This article is a brief attempt re-vamp Mr. Castiel's markup. Admittedly, I haven't changed much. In fact, I think I've made an objectively worse "generator." All I've done is:

  1. Use CSS custom properties whenever possible.
  2. Use aspect ratio rather specific heights.
  3. Adjusted border-radius values.
  4. Extract all important values to custom properties, and calc the rest.

What does the html look like?

The html is straightforward: two containers, an image, and two pseudo-elements. book-container lies nestled snugly in book-container. Perhaps it should be called book-stand, but as an industry we're addicted to the word container, aren't we? Remember, you don't need to write ::before or ::after; they are just for illustration. Additionally, book-container can be an anchor, if you'd like to link off to another page.

 <div class="book-container">
    <div class="book">
        ::before
        <img
            alt="Alt text."
            src="${sourceUrl}"
        />
        ::after
        </div>
    </div>
</div>

A quick overview

The ::before pseudo-element is transformed (rotated and translated) given a linear-gradient background so as to resemble pages. The image naturally forms the front cover. The ::after pseudo-element is transformed (translated) to form the back cover. The whole thing has a rotated on hover animation applied. There is no spine to the book, so our book is a bit of a house of cards. More work would be required if you wanted a true 3d representation.

The "generator"

I have assigned the following CSS properties to allow us to quickly and easily customize our book. You can edit the code block below and see the effects. It is slightly disingenuous to call this a generator, but I hope you'll forgive me.

html {
    --book-cover-width:  200px;
    --book-page-top: 3px;
    --book-jacket-color: #01060f;
    --book-cover-radius: 6px;
    --book-spine-radius: 2px;
    --book-front-cover-background-color: #01060f;
    --book-thickness: 50px;
    --page-depth: 3px;
    --book-aspect-ratio: 2/3;
    --object-fit: cover;
}

Sand dunes

The following CSS should not change at all. You'll only need to use the above CSS custom properties and html markup to get the desired book-look!

.book-container {
  display: flex;
  align-items: center;
  justify-content: center;
  perspective: 800px;
}

@keyframes initAnimation {
  0% {
    transform: rotateY(0deg);
  }
  100% {
    transform: rotateY(-30deg);
  }
}

.book {
  aspect-ratio: var(--book-aspect-ratio);
  width: var(--book-cover-width);
  position: relative;
  transform-style: preserve-3d;
  transform: rotateY(-30deg);
  transition: 1s ease;
  animation: 1s ease 0s 1 initAnimation;
}

.book-container:hover .book,
.book-container:focus .book {
  transform: rotateY(0deg);
}

.book > img {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  background-color: var(--book-front-cover-background-color);
  width: var(--book-cover-width);
  aspect-ratio: var(--book-aspect-ratio);
  transform: translateZ(calc(var(--book-thickness) / 2));
  border-end-start-radius: var(--book-spine-radius);
  border-start-start-radius: var(--book-spine-radius);
  border-start-end-radius: var(--book-cover-radius);
  border-end-end-radius: var(--book-cover-radius);
  box-shadow: 5px 5px 20px #666;
  object-fit: var(--object-fit);
}

.book::before {
  position: absolute;
  content: " ";
  background-color: blue;
  inset-inline-start: 0;
  inset-block: var(--book-page-top);
  width: calc(var(--book-thickness) - 2px);
  transform: translateX(
      calc(
        var(--book-cover-width) - var(--book-thickness) / 2 - var(--page-depth)
      )
    ) rotateY(90deg);
  background: linear-gradient(
    90deg,
    #fff 0%,
    #f9f9f9 5%,
    #fff 10%,
    #f9f9f9 15%,
    #fff 20%,
    #f9f9f9 25%,
    #fff 30%,
    #f9f9f9 35%,
    #fff 40%,
    #f9f9f9 45%,
    #fff 50%,
    #f9f9f9 55%,
    #fff 60%,
    #f9f9f9 65%,
    #fff 70%,
    #f9f9f9 75%,
    #fff 80%,
    #f9f9f9 85%,
    #fff 90%,
    #f9f9f9 95%,
    #fff 100%
  );
}

.book::after {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  content: " ";
  aspect-ratio: var(--book-aspect-ratio);
  width: var(--book-cover-width);
  transform: translateZ(calc(var(--book-thickness) / 2 * -1));
  background-color: var(--book-jacket-color);
  border-end-start-radius: var(--book-spine-radius);
  border-start-start-radius: var(--book-spine-radius);
  border-start-end-radius: var(--book-cover-radius);
  border-end-end-radius: var(--book-cover-radius);
  box-shadow: -10px 0 50px 10px #666;
}