DEV Community

Cover image for Create animated columns of tiles with a background-images fade in and fade out
Andrew Saeed
Andrew Saeed

Posted on • Edited on

Create animated columns of tiles with a background-images fade in and fade out

In this blog post, I will show you how I have created animated columns of tiles that smoothly transition their background images. This technique not only adds depth and interest to your web page but also provides an elegant way to showcase multiple images or content pieces in a limited space.

A basic understanding of HTML, CSS, and JavaScript will be sufficient to follow along. Let's dive in and explore how to create these visually appealing, animated columns.

Allow me to show you!

Content

  • What are we building?
  • Painting Our HTML with Tailwind (Utility-first CSS framework).
  • Write some JavaScript to make it animate.
  • Summary and code-sample.

What are we building?


We are building a row of interactive columns. Each column will hold two tiles (an upper tile and a lower tile). When the user hovers over a tile, it will expand its height to take up all the space of its parent column. Then, a new background image will fade in—this could be an image of a product, for example.

Additionally, I will wrap all the columns inside a responsive slider (using Swiper) to control the number of columns displayed on different screen sizes.

Painting Our HTML with Tailwind.

To get started, we'll need some data to work with. This data will be used to create a loop and dynamically generate the needed HTML. Here's the JavaScript columns array I'll be using for this purpose:

const columns = [
    ...
    {
        tiles: [
            {
                positionClass: 'top-0',
                tileClass: 'top-tile',
                tileMaskClass: 'top-tile_mask',
                bgImageExpr: "bg-[url('/bmw.png')]",
                bgColor: 'bg-black-dark',
                logo: 'bmw-logo.svg'
            },
            {
                positionClass: 'bottom-0',
                tileClass: 'bottom-tile',
                tileMaskClass: 'bottom-tile_mask',
                bgImageExpr: "bg-[url('/chevrolet.png')]",
                bgColor: 'bg-black-light',
                logo: 'chevrolet-logo.svg'
            }
        ]
    }
    ...
]
Enter fullscreen mode Exit fullscreen mode

Every tile object within columns array has the necessary props for styling:

  • positionClass prop has top-0 and bottom-0 tailwind classes for positioning the tile to the top or bottom.
  • tileClass prop has top-tile class or bottom-tile class. They are needed by JavaScript Dom selectors.
  • tileMaskClass prop has top-tile_mask class or bottom-tile_mask class. They are also needed by JavaScript Dom selectors.
  • bgImageExpr prop will set the background-image of that tile using the tailwind expr bg-[url('')].
  • bgColor prop provides a background-color for the tile's mask.
  • logo prop provides the logo svg for the tile's mask.

Now we can map the columns array to HTML:

<div class="swiper w-full h-[27.5rem] max-w-[13.75rem] md:max-w-[41.25rem] lg:max-w-[55rem] xlg:max-w-[68.75rem]">

    <div class="swiper-wrapper">

        { columns.map( column => {

            return <div class="swiper-slide">
                <div class="single-column relative w-[13.75rem] h-[27.5rem] overflow-hidden">

                    { column.tiles.map( tile => {

                        return <div class:list={[
                            tile.tileClass, 
                            `w-full h-1/2 absolute ${tile.positionClass} left-0`, 
                            `${tile.bgImageExpr}`,  
                            "bg-center border border-black-light transition-[height] duration-300 ease-out"
                        ]}>
                            <div class:list={[
                                tile.tileMaskClass, 
                                "w-full h-full flex items-center justify-center", 
                                tile.bgColor, 
                                "bg-[length:7.5rem] bg-center bg-no-repeat transition duration-300 ease-out"
                            ]} style={`background-image: url('/${tile.logo}');`}>
                            </div>
                        </div>
                    }) }
                </div>
            </div>
        }) }
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Write some JavaScript to make it animate.

When the user interacts with the tiles we want to modify their appearance. To achieve that we will attach event listeners to elements .top-tile_mask and .bottom-tile_mask.

In Case of .top-tile_mask element:

  • On mouseenter, I will set the opacity of the top tile's mask to 0 (making it invisible), change the z-index of its parent (.top-tile element) to 10, and also set that parent's height to 100%.
  • On mouseout, I will set the opacity of the mask back to 1 (making it visible again) and reset its parent's height to 220px.

In Case of .bottom-tile_mask element:

  • On mouseenter, I will set the opacity of the bottom tile's mask to 0, unset the z-index of the .top-tile element, and set the .bottom-tile_mask parent's height to 100%.
  • On mouseout, I will set the opacity of the bottom tile's mask back to 1 and reset its parent's height to 220px.

Here's the JavaScript code that implements this functionality:

this.$el.querySelectorAll<HTMLElement>('.single-column').forEach( column => {

    const topTileMask = column.querySelector<HTMLElement>('.top-tile_mask')!
    const bottomTileMask = column.querySelector<HTMLElement>('.bottom-tile_mask')!

    if(topTileMask && topTileMask.parentNode) {
        topTileMask.addEventListener('mouseenter', () => {
            topTileMask.style.opacity = '0';
            (topTileMask.parentNode as HTMLElement).style.zIndex = '10';
            (topTileMask.parentNode as HTMLElement).style.height = '100%';
        })

        topTileMask.addEventListener('mouseout', () => {
            topTileMask.style.opacity = '1';
            (topTileMask.parentNode as HTMLElement).style.height = '220px';
        })
    }

    if(bottomTileMask && bottomTileMask.parentNode) {
        bottomTileMask.addEventListener('mouseenter', () => {
            bottomTileMask.style.opacity = '0';
            (topTileMask.parentNode as HTMLElement).style.zIndex = 'unset';
            (bottomTileMask.parentNode as HTMLElement).style.height = '100%';
        })

        bottomTileMask.addEventListener('mouseout', () => {
            bottomTileMask.style.opacity = '1';
            (bottomTileMask.parentNode as HTMLElement).style.height = '220px';
        })
    }
})
Enter fullscreen mode Exit fullscreen mode

Note: this.$el is there because I'm using Alpinejs to wrap all the code inside a component. $el is a ref to the root element.

Summary

With our HTML structure and JavaScript in place, the animated columns are now fully functional. When users interact with the tiles, the animations will trigger, smoothly transitioning the background images and adjusting the tile sizes. The right styles will be dynamically applied to the elements, creating the visually appealing effect we set out to achieve.

🙏 Your feedback is much appreciated

Full Code

Working Example

ko-fi

Top comments (0)