Hardcover Book Marquee Experiment

Hardcover Book Marquee Experiment

October 28, 2024 at 3:40 PM

Using the Hardcover API to display a list of books in a marquee

Introduction

I have been using the Hardcover app for a while now to keep track of the books I read.
Hardcover is a great app for organizing your reading list and discovering new books.

I thought it would be fun to experiment with the Hardcover API and see if I could create a marquee of book covers to display on my website or with-in the Hardcover application.

In this experiment, I will walk you through the process of fetching data from the Hardcover API and rendering a list of book covers into a scrolling marquee.

I am using React and Tailwind CSS for this experiment, but you can adapt the code to any other framework or library you prefer.

Let’s take a look at the final result before we dive into the details:

Important note about the Hardcover API

The Hardcover API is currently in beta and is subject to change.

The API is also rate-limited to prevent abuse, so be mindful of the number of requests you make.

Getting started

To get started, you will need to create an account on Hardcover then you will need to create a new list and add some books to it.

Once you have added some books to your list, you will need to generate an API key.
To do this, navigate to your account settings page and click on the “Hardcover API” tab.

You will see your API token at the top of the page.
Copy this token as we will need it later, but be sure to keep it safe as it is a secret key tied to your account.

The Hardcover API URL is https://api.hardcover.app/v1/graphql


Making requests to the Hardcover API

To make requests to the Hardcover API, we will need to include our API token in the request headers.
We will also need to specify the GraphQL endpoint for the API.

Here is an example of how you can make a request to the Hardcover API using the fetch API:

fetch(GRAPHQL_URL, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': AUTH_TOKEN.startsWith('Bearer') ? AUTH_TOKEN : `Bearer ${AUTH_TOKEN}`
    },
    body: JSON.stringify({
        query: QUERY_STRING
    })
})
.then(response => response.json())
.then(data => {
    console.log(data);
})
.catch(error => {
    console.error(error);
});

Remember to replace GRAPHQL_URL, AUTH_TOKEN, and QUERY_STRING with the appropriate values.

It is a good idea to store your API token in a secure location and not hardcode it into your application.
You can use environment variables or a secure secrets management service to store your API token.

If you are using a frontend framework like React, you can create a custom hook to handle the API requests.

Alternatively, you can use a library like Apollo Client to handle the API requests for you.


Fetching the lists belonging to your account

Hardcover uses a GraphQL API to fetch data. To get a list of the lists belonging to your account, you can use the following query:

query GetMyLists {
    me {
        lists {
            id
            name
            books_count
        }
    }
}

Fetching the books belonging to a specific list

Once we have the list of lists, we can then fetch the books belonging to a specific list.
To do this, we will need to use the id of the list we want to fetch the books from.

Using the following query, we will fetch the title and cover image URL of each book.

query GetListBooks {
    list_books(
        where: {
            list_id: {_eq: ${bookListId}}
        }
    ) {
        edition {
            cached_image
            title
        }
    }
}

Remember to replace ${bookListId} with the id of the list you want to fetch the books from.


Rendering the book covers

Now that we have the list of books, we can render the book covers.
To do this, we will need to use the cachedImage.url field of each book.

We will use the title and author fields as alt text for the images to provide context for screen readers.

We can then render the book covers using the following code:



Creating the marquee columns

Currently, the book covers are rendered in a single row.
We want to show the books in multiple columns to create our scrolling effect.

Once we know the number of columns we want to display, we can create the columns using the following code:


Now that we have the columns defined, we can render the book covers in each column.



Creating the frame for the marquee

We want to limit the height of the marquee and add a gradient fade effect to the top and bottom
to make it look like the books are scrolling in and out of view.

To do this, let’s add some CSS to our marquee container:

.book-marquee {
    background-color: var(--color-bg);
    overflow: hidden;
    height: 600px;
    mask-image: linear-gradient(
        var(--mask-direction, to left),
        hsl(0 0% 0% / 0),
        hsl(0 0% 0% / 1) 20%,
        hsl(0 0% 0% / 1) 80%,
        hsl(0 0% 0% / 0)
    );
}

Animating the marquee

Now that we have the container set up, we can add an animation to make the marquee scroll.
We can use the @keyframes rule to define the animation and apply it to the marquee container.

.book-marquee {
    ...

    .marquee {
        ...

        .group {
            display: flex;
            animation: scroll-y var(--duration) linear infinite;
        }
    }
}


@keyframes scroll-y {
    from {
        transform: translateY(var(--scroll-start));
    }
    to {
        transform: translateY(var(--scroll-end));
    }
}

Alternating the direction of the marquee columns

To make the marquee more interesting, we can alternate the direction of the columns.
This will create a more dynamic effect as the books scroll across the screen.

Let’s add some CSS to alternate the animation direction of the columns:

.book-marquee {
    ...

    .marquee {
        ...

        .group {
            ...

            &:nth-child(odd) {
                animation-direction: reverse;
            }
        }
    }
}

Respecting the user’s preferences

When animating the marquee, it is important to respect the user’s preferences.
Some users may have motion sensitivity or other conditions that make animations difficult to watch.

To respect the user’s preferences, we can use the prefers-reduced-motion media query to disable the animation if the user has requested it.

@media (prefers-reduced-motion: reduce) {
    .book-marquee {
        .marquee {
            .group {
                animation-play-state: paused;
            }
        }
    }
}

Theming the marquee for light and dark mode

To theme the marquee for light and dark mode, we can use the prefers-color-scheme media query.

This allows us to change the colors of the marquee based on the user’s system preferences.

:root {
    --color-bg: var(--color-bg-light);
    --color-bg-accent: rgb(61, 93, 218);
    --size: clamp(2rem, 1rem + 40vmin, 13rem);
    --gap: calc(var(--size) / 25);
    --duration: 90s;
    --scroll-start: 0;
    --scroll-end: calc(-100% - var(--gap));
}

@media (prefers-color-scheme: dark) {
    :root {
        --color-text: papayawhip;
        --color-bg: rgb(37, 38, 47);
        --color-bg-accent: rgb(61, 93, 218);
    }
}

Going further

The Hardcover API imposes rate limits on requests to prevent abuse.
As such, you may want to cache the data on your server to reduce the number of requests made to the API.

You could also experiment with different layouts and animations to create a unique marquee experience,
perhaps adding hover effects, click interactions, or scrolling in a different direction.

For more information on the Hardcover API, you can check out the Hardcover API documentation.

I hope you have enjoyed this experiment and found it useful.

This is the first in a series of experiments I have done using the Hardcover API. To see the second experiment, check out the Hardcover Book Cards Experiment.

Additional experiments using the Hardcover API will be coming soon, so stay tuned to the Hardcover project page!