Bento Layouts Tilt Me

I was recently playing around with Bento layouts (if you don’t know what Bento layout / grid is, it’s the new trend all the trendy designers are doing, catch up) and actually automating the layout process - basically using grid-auto-flow on grid layout. Then I got an idea: “Could I make the tiles dynamically tilt towards the mouse pointer?” Why? - Why not? Let’s do it.

Bento layouts actually don’t tilt me, sorry. I just wanted to use this awesome clickbait opportunity for what I’ve made. This semi-demo and tutorial will guide you on achieving this interactive effect.

Making a Single Tile Tilt

Tilting a single tile is straightforward, and a talented developer - Armando Canals, already did that.

All I needed to do was scale it by adding multiple tiles, inside a grid layout. The code is here, with the relevant parts described in the comments:

Bento layout with tiles tilting toward the cursor


<style>
    body {
        background: #302c31;
    }
    .container {
        height: 100%;
        width: 100%;
        /* Make it a grid */
        display: grid;

        /* 8 columns and 4 rows */
        grid-template-columns: repeat(8, 100px);
        grid-template-rows: repeat(4, 100px);

        /* Make the layout dense = fill out any "holes" and leave uncompleted rows if necessary */
        /* Try commenting the dense option out to see the difference */
        grid-auto-flow: dense;

        align-content: center;
        justify-content: center;
        gap: 8px;
    }

    .box {
        background: white;
    }

    /* Here we make some if the tiles different size */
    /* to see the effect of the grid layout */
    .box:is(:nth-child(2), :nth-child(8), :nth-child(10)) {
        grid-column: span 2;
        grid-row: span 1;
    }

    .box:is(:nth-child(3), :nth-child(7), :nth-child(13)) {
        grid-column: span 2;
        grid-row: span 2;
    }
</style>
<body>
<!--    Spawn multiple boxes -->
    <div class="container" id="container">
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
    </div>
</body>
<script>
    // Try smaller numbers to see more tilt
    let constrain = 30;
    // the part where mouse movement will be taken into account
    // replace with 'body' to consider the whole body of the webpage
    let mouseOverContainer = document.getElementById("container");
    // select all our boxes
    let boxes = document.getElementsByClassName("box");

    function transforms(x, y, el) {
        let box = el.getBoundingClientRect();
        let calcX = -(y - box.y - (box.height / 2)) / constrain;
        let calcY = (x - box.x - (box.width / 2)) / constrain;

        // try smaller perspective for more tilt. (may produce some fun artifacts)
        return "perspective(500px) "
            + "   rotateX("+ calcX +"deg) "
            + "   rotateY("+ calcY +"deg) ";
    }

    function transformElement(el, xyEl) {
        el.style.transform  = transforms.apply(null, xyEl);
    }

    mouseOverContainer.onmousemove = function(e) {
        let xy = [e.clientX, e.clientY];
        window.requestAnimationFrame(function(){
            // Calculate the transform for all our boxes
            for (const box of boxes) {
                let position = xy.concat([box]);
                transformElement(box, position);
            }
        });
    };
</script>

Bonus: Making the Highlight Pop

We can enhance the layout’s visual appeal - the tile can animate to pop out when hovered. To do that, we check whether we’ve hovered a smaller area inside the tile, and if so, we scale it. Simple and effective. To try it out, just replace your transforms function with the one below.

function transforms(x, y, el) {
let box = el.getBoundingClientRect();
let calcX = -(y - box.y - (box.height / 2)) / constrain;
let calcY = (x - box.x - (box.width / 2)) / constrain;

        // try smaller perspective for more tilt. (may produce some fun artifacts)
        let transformString = "perspective(500px) "
            + "   rotateX("+ calcX +"deg) "
            + "   rotateY("+ calcY +"deg) ";

        // make the hover area half the tile's bounding box
        const halfBox = {
            left: box.left + box.width / 4,
            right: box.right - box.width / 4,
            top: box.top + box.height / 4,
            bottom: box.bottom - box.height / 4,
        }

        // if the mouse is within the half-BBox, 
        // don't apply the rotation, but scale the tile instead
        if (x >= halfBox.left && x <= halfBox.right
            && y >= halfBox.top && y <= halfBox.bottom) {
            transformString = "   scale(2) "
        }
        return transformString;
    }
The final result

The End

Thank you for diving into this quick tutorial. Now that you are a cool kid, tweet (X?) me your creative Bento grid designs @devslovecoffee.

Explore my other posts and projects for more creative web design ideas ☕.