Search code examples
cssimagecss-filters

Keep text unaffected by css filter effect


When I apply a filter to darken the background of an image to make the text readable, it applies to everything within the div.

.blog-container{
    height: 400px;
    position: relative;
    display: inline-block;
    color: #fff;
    width: 280px;
    background-size: 100% 100%;
    border: 1px solid #000;
    border-radius: 20px;
    background-image: url("https://images.unsplash.com/photo-1688297969967-302de9c8bd5b?q=80&w=1887&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D");
}

.blog-container:hover{
    cursor: pointer;
    transition: 0.5s;
    filter: brightness(55%) saturation(140%); /* only way I've managed to darken the background */

}

.blog-title, .blog-desc{
    width: 260px;
    position: absolute;
    bottom: 8px;
    left: 18px;
    opacity: 0;
}

.blog-title{
    font-weight: 200;
    font-size: 25px;
    bottom: 45px;
}

.blog-container:hover .blog-title {
    -webkit-animation: slide-up 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0s forwards;
}

.blog-container:hover .blog-desc{
    -webkit-animation: slide-up 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s forwards;
}

@-webkit-keyframes slide-up {
    0% { -webkit-transform: translateY(80%); opacity: 1 }
    100% { -webkit-transform: translateY(0); opacity: 1  }
}

I'm expecting that when I hover over the div, the background-image darkens and the text slides in and is visible


Solution

  • Good effort! You're experiencing this issue because of how CSS inherently works: when you apply styling to a parent element, it naturally affects all the children inside it. This includes any filters or effects you add.

    Here's my suggested approach:

    To get around this, we should introduce a kind of 'intermediary' element. I've used .blog-container::before in this case, to dynamically generate an additional element. (You can read more about this approach in the MDN doc's - CSS ::before).

    We need to ensure we apply our visual changes at the same hierarchical level as your other child elements, like the title and description.

    This ::before element is set to position: absolute, while its parent .blog-container is position: relative;. This allows us to position the ::before element independently of the document flow. (MDN Doc's - CSS Position)

    Then, we ensure this pseudo-element covers the entire parent (See Snippet). Don't forget to set overflow: hidden on the parent to prevent any spillover.

    Now that we've done that we can target ::before element's hover effect to tell it what to do when the element has hover; independently of other children. - Magic!!

    Lastly, we sort out the stacking order. We do this by adjusting the z-index properties. We set the z-index of the ::before element and the textual elements you want to appear on top. This ensures that the text is always visible and unaffected by the background effect.

    .blog-container {
        height: 400px;
        position: relative;
        display: inline-block;
        color: #fff;
        width: 280px;
        background-size: 100% 100%;
        border: 1px solid #000;
        border-radius: 20px;
        background-image: url("https://images.unsplash.com/photo-1688297969967-302de9c8bd5b?q=80&w=1887&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D");
        overflow: hidden; /* Added to contain the pseudo-element */
    }
    
    .blog-container::before {
        content: '';
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: inherit;
        filter: brightness(55%) saturate(140%);
        transition: opacity 0.5s ease;
        opacity: 0;
        z-index: 1;
    }
    
    .blog-container:hover::before { /* The magic line */
        opacity: 1;
    }
    
    .blog-container:hover {
        cursor: pointer;
        transition: 0.5s;
    }
    
    .blog-title, .blog-desc {
        width: 260px;
        position: absolute;
        bottom: 8px;
        left: 18px;
        opacity: 0;
        z-index: 2; /* Ensure text is above the overlay */
    }
    
    .blog-title {
        font-weight: 200;
        font-size: 25px;
        bottom: 45px;
    }
    
    .blog-container:hover .blog-title {
        -webkit-animation: slide-up 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0s forwards;
    }
    
    .blog-container:hover .blog-desc {
        -webkit-animation: slide-up 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s forwards;
    }
    
    @-webkit-keyframes slide-up {
        0% { -webkit-transform: translateY(80%); opacity: 1 }
        100% { -webkit-transform: translateY(0); opacity: 1 }
    }
    <div class="blog-container">
      <h1 class="blog-title">
        Hello World!
      </h1>
      <span class="blog-desc">Lorum ipdum et sumit.</span>
    </div>