0

I have a code where the "card" class takes the user to an article when clicked. And its child, "category", takes the user to another website when clicked.

<div class="card">
    <div class="img"></div>
    <div class="category"></div>
    <div class="title"></div>
    <div class="description"></div>
</div>

enter image description here

I'm trying to write CSS animations with div:active for these two divs. So when I have the following, the whole card animates:

.card:active {
    transform: translateX(50px);
    transition: all 0.5s ease;
}

But I don't want the card to animate when the user clicks on the category div. So, I tried something like the following, and others, which didn't work.

:not(.category).card:active{
    transform: translateX(50px);
    transition: all 0.5s ease;
}

Is there a combination of :not and :active pseudo classes that I could use to make the card animate when clicked, but not animate when the category is being clicked?

1 Answers1

1

solution 1, CSS only

Generally one could use...

/* CSS */
.card                 { pointer-events: none }
.card>:not(.category) { pointer-events: auto } /* all kids except .category */
.card:active          { transform: translateX(50px) }

/* HTML */
<div class="card">
    <div class="img">image</div>
    <div class="category">CATEGORY</div>
    <div class="title">no-card</div>
    <div class="description">description</div>
</div>

...and clicking any child of .card, except class .category, will trigger the card :active event as well as :hover. However, any card space not occupied by child elements wil not trigger any event (i.e. .card:padding and .category will not trigger :active or :hover).

Another drawback is that .category will listen to no events at all and therefore cannot be an input element that needs to handle those events (like a <button>, as shown in the demo).

If this is acceptable, this solution is the easiest to code and maintain.

solution 2, CSS plus JS

This solution uses only simple CSS...

.effect:active { transform: translateX(50px) } /* NOT .card:active */

...and some Vanilla Javascript (pseudo code) that simply removes/adds the .effect class from .card when appropriate.

forEach cardList.item do
   card.onmouseover = enableEffect();

   card.category.onmouseenter = disableEffect();
   card.category.onmouseout   = enableEffect();

disableEffect = remove class 'effect' from .card
enableEffect  = add class 'effect' to .card

The below snippet includes both solutions, is heavily commented and includes a few responsiveness extras (like CSS columns, main font and page spacing. Math used MathIsFun: Linear Equation).

Just copy the code and have fun with it!

SNIPPET

'use-strict';

// Traverse an array and execute the passed callback function for each array element found
var forEachEntryIn = function (array, callback, scope) {
        for (var i = 0; i < array.length; i++) { callback.call(scope, i, array[i]); } };

// Get the list of cards
var cards = document.getElementsByClassName('card');

// Make this a function and you can toggle it with a <button>
var DEBUG = false; // set to 'true' for debug view and some console output
(DEBUG) ? document.body.setAttribute('outlines','1') : document.body.setAttribute('outlines','0');

// Traverse the list of cards
forEachEntryIn( cards,
    function (idx,card,scope) {

        // '.effect' is needed by default,
        // so why add it in HTML class="" property when we can do it here...
        card.classList.add('effect'); // remove if you want to assign in HMTL anyway

        /*
            MOUSEOVER events are bubbled to child elements
            MOUSEENTER does not bubble, needed on '.category'

            target: the element that triggered the event ('.card' OR any of its child elements)
            currentTarget: the element that the event listener is attached to: '.card'
        */
        card.onmouseover = function(e) { // Attach 'MOUSEOVER' listener to '.card'
            // Parent check: event may be bubbled (from any '.card' children)
            // So, is the parent a '.card' or maybe its parent?
            if (e.target.parentElement == e.currentTarget) {
                enableEffect(e.target.parentElement); // Activate '.card' animation
            }; 
            // NOTE: Disable the check, click a card and see what happens....funny!
        };

        var category = card.querySelector('.category');

        if (card.contains(category)) {
            category.onmouseenter = function(e) { disableEffect(e.currentTarget.parentElement); };
            category.onmouseout   = function(e) { enableEffect (e.currentTarget.parentElement); };
        };

    } // end function (idx,el,scope)
); // end forEachEntryIn

// Helper functions to keep main loop readable
function enableEffect(parent) {
    if (!parent.classList.contains('effect')) { // if parent has no '.effect'
        parent.classList.add('effect'); // then add it
    };
    if (DEBUG) logInfo(parent);
};

function disableEffect(parent) {
    if (parent.classList.contains('effect')) { // parent if has '.effect'
        parent.classList.remove('effect'); // then remove it
    };
    if (DEBUG) logInfo(parent);
};

// For debugging
function logInfo(p) {
    console.log( // Show some info in browser console
        ((p.className) ? '<' + p.tagName +' class="' + p.className + '">': '<' + p.tagName +'>' ),
        p.classList.contains('effect')
    );
};
/********************************/
/* demo for CSS only solution 1 */
/********************************/
.no-card {
    pointer-events: none;
} 
.no-card>:not(.category) {
    pointer-events: auto;
}
.no-card:active {
    transform: translateX(50px);
    transition: all 0.5s ease;
}
/***********************************/
/* demo for CSS plus JS solution 2 */
/***********************************/
/* class will be assigned with JS */
.effect:active {
    transform: translateX(50px);
    transition: all 0.5s ease;
}


/*****************************************************/
/* below just demo, everything can be safely removed */
/*****************************************************/


/**************************/
/* preferred global rules */
/**************************/
html,body               { box-sizing: border-box; width: 100%; max-width: 100% }
*::before,*::after, *   { box-sizing: inherit }
body                    { margin: 0 }

/*  ALL math reference: https://www.mathsisfun.com/equation_of_line.html */

/* responsive base font size using y = mx + b */
html   { font-size: calc(0.625vmin + 0.75rem) } /* (320,14)(1280,20) */

/* prohibit user from selecting text (put in <body>) */
[no-select] { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none }
[do-select] { -webkit-user-select: text; -moz-user-select: text; -ms-user-select: text; user-select: text; cursor: auto }
/* enable user to select text (put in specific elements) */

/* to show all elements with outlines (assigned to <body> with JS) */
[outlines="1"] * { outline: 1px dashed }

/***********************************/
/* Extra: plain responsive columns */
/***********************************/
body {
    /*
        responsive page padding using y = mx + b
        p1(320,32) p2(1920, 72) => y = 0.025x + 24
        p3(320, 8) p4(1920,320) => y = 0.195x - 54.4 
    */
    padding: calc(2.5vh + 24px) calc(19.5vw - 54.4px);

}
.cardList {
    column-count: 3;    /* preferred number of columns given column-width */
    column-gap: 0;      /* handled with card margins */

    /*
        column width using y = mx + b

        mobile/tablet, 1 column : 320 - 60 = 260px
              desktop, 3 columns: (1920 - 640) / 3 = 426 minus animation gap = 376px

        p1(320,260) p2(1920,376)
        => y = 7.25x + 236.8
    */
    column-width: calc(7.25vw + 230.8px); /* (320,260)(1920,376) */
    /* (320,260)(1920,376) for scrollbar => 236.8 - (18/3) = 230.8px */
}
.card {
    break-inside: avoid; /* don't split card over columns */
}

/******************/
/* card eye-candy */
/******************/
.wrapper,
.cardList {
    background-color: rgba(0,0,0,.1); /* just to review body padding */
    padding: 2rem 0;
}

.no-card, .card {
    background-color: CornSilk;

    padding: 1rem;
    margin : 1rem;
    margin-right: 60px;  /* animation width plus 10px space */

    /* GMC elevation 1dp */
    box-shadow: 0px 2px 1px -1px rgba(0,0,0,.20),
                0px 1px 1px  0px rgba(0,0,0,.14),
                0px 1px 3px  0px rgba(0,0,0,.12);
}
.card:first-child { margin-top: 0 } /* otherwise jagged column tops */

/* Some :hover animation */
.no-card:hover, .card:hover {
    /* GMC elevation 3dp */
    box-shadow: 0px 3px 3px -2px rgba(0,0,0,.20),
                0px 3px 4px  0px rgba(0,0,0,.14),
                0px 1px 8px  0px rgba(0,0,0,.12);
}
<body no-select>

<h2>solution 1, CSS only</h2>
<div class="wrapper">
    <div class="no-card">
        <div class="img">image</div>
        <button class="category">CATEGORY</button>
        <div class="title">no-card</div>
        <div class="description">description</div>
    </div>
</div>

<h2>solution 2, CSS plus JS</h2>
<div class="cardList">
    <div class="card">
        <div class="img">image</div>
        <button class="category">CATEGORY</button>
        <div class="title">card 1</div>
        <div class="description">description</div>
    </div>

    <div class="card">
        <div class="img">image</div>
        <button class="category">CATEGORY</button>
        <div class="title">card 2</div>
        <div class="description">description</div>
    </div>

    <div class="card">
        <div class="img">image</div>
        <button class="category">CATEGORY</button>
        <div class="title">card 3</div>
        <div class="description">description</div>
    </div>

    <div class="card">
        <div class="img">image</div>
        <button class="category">CATEGORY</button>
        <div class="title">card 4</div>
        <div class="description">description</div>
    </div>

    <div class="card">
        <div class="img">image</div>
        <div>some other element</div>
        <div class="title">card 5</div>
        <div class="description">description</div>
    </div>

    <div class="card">
        <div class="img">image</div>
        <button class="category">CATEGORY</button>
        <div class="title">card 6</div>
        <div class="description">description</div>
    </div>
</div>
</body>
Rene van der Lende
  • 4,992
  • 2
  • 14
  • 25