CSS :active 和 :not psuedo 的组合 类

CSS combination of :active and :not psuedo classes

我有一个代码,其中“卡片”class 在单击时将用户带到一篇文章。 其子项“类别”在单击时将用户带到另一个网站。

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

我正在尝试用 div:active 为这两个 div 编写 CSS 动画。 所以当我有以下内容时,整张卡片都会动画:

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

但我不希望卡片在用户单击 类别 div 时显示动画。 所以,我尝试了类似下面的方法和其他方法,但都没有用。

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

是否有 :not 和 :active pseudo classes 的组合,我可以使用它来使 card 在单击时具有动画效果,但是 类别 被点击时不 动画?

解决方案 1,仅 CSS

一般可以用...

/* 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>

...并点击 .card 的任何子项,除了 class .category,将触发卡片 :active 事件以及 :hover。但是,任何未被子元素占用的卡片 space 将 不会触发任何事件 (即 .card:padding.category 不会触发 :active:hover).

另一个缺点是 .category 根本不会监听任何事件,因此不能成为需要处理这些事件的输入元素(如 <button>,如演示中所示)。

如果这是可以接受的,则此解决方案最容易编码和维护。

方案二,CSS加JS

此解决方案仅使用简单的 CSS...

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

...和一些 Vanilla Javascript(伪代码),在适当的时候简单地 removes/adds 来自 .card.effect class。

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

下面的代码片段包括这两种解决方案,有大量评论并包括一些额外的响应能力(例如 CSS 列、主要字体和页面间距。使用的数学 MathIsFun: Linear Equation)。

只需复制代码,尽情享受吧!

片段

'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>