單標籤下的日間/黑夜模式切換按鈕效果

前不久,在網上看到這麼一張非常有趣的圖:

想必很多同學都看到這張圖,是一個開發小哥被一個日間/黑夜模式切換按鈕效果逼瘋的視頻。

其最終效果大致如下:

原完整代碼在這裏:Night && Day Toggle ☀️/🌙 [Completed It!]

原效果用了大量 HTML 標籤,大量 SVG 元素以及 350 行的 CSS 完成的上述效果。

而本文,我們將嘗試優化一下代碼,嘗試僅僅使用一個標籤,完成上述效果。

當然,首先,我們需要一個標籤:

<div></div>

接下來,在單個標籤內,我們一步一步來實現這個效果。

擬態陰影

先把整個按鈕的形狀確定下來,我們需要這樣一個整體的擬物形狀:

可以看到,這個造型非常的立體。這裏的核心是 -- 利用陰影,構建擬態效果

怎麼操作呢?其原理就在於,使用兩組陰影,使用兩個相反的方向,使用兩組對比明顯的顏色值,來實現凹凸效果。

我們需要使用盒子的內陰影實現。

看個例子:

<div>浮雕陰影</div>
<div>浮雕陰影</div>
div {
    width: 120px;
    height: 120px;
    background: #e9ecef;
    color: #333;
    box-shadow:
        7px 7px 12px rgba(0, 0, 0, .4),
        -7px -7px 12px rgba(255, 255, 255, .9);
}

div:nth-child(2) {
    box-shadow:
        inset -7px -7px 12px rgba(255, 255, 255, .9),
        inset 7px 7px 12px rgba(0, 0, 0, .4);
}

這樣,就可以得到擬態風格的按鈕,如下圖所示,左凸右凹:

借鑑這個方式,我們很快就能得到整個按鈕的外形代碼:

body {
    width: 100%;
    height: 100%;
    background: #d9deea;
}
div {
    width: 220px;
    height: 90px;
    border-radius: 90px;
    box-shadow: 
        0 -3px 4px #999,
        inset 0 3px 5px #333,
        0 4px 4px #ffe,
        inset 0 -3px 5px #ddd;
}

這樣,整個外框就實現了:

日間模式的實現

好,接下來,我們來實現日間模式,其整個效果如下:

仔細觀察上述圖形,除了外框外,主要還有幾大部分:

  1. 一個圓形太陽
  2. 太陽的光暈
  3. 雲朵效果

發現了嗎?它們都是圓形!而在 CSS 中,能夠利用單個屬性構建多個圓形的方式有非常多種:

  1. box-shadow
  2. filter: drop-shadow()
  3. background 漸變

並且,上面我們只使用了 div 本身,還有兩個僞元素沒有使用。我們需要充分把這兩個僞元素利用起來。這裏,我們這樣分工一下:

  1. 僞元素 ::before: 用於實現太陽本身
  2. 僞元素 ::after:用於實現太陽的光暈及雲朵效果

我們一步一步來。

利用僞元素 ::before: 實現太陽本身

這個還是非常好理解的,直接上代碼:

div::before {
    content: "";
    position: absolute;
    width: 75px;
    height: 75px;
    border-radius: 50%;
    background: #e9cb50;
    inset: 7.5px;
    box-shadow: 
        0 0 5px #333,
        inset 2px 2px 3px #f8f4e4,
        inset -2px -2px 3px #665613;
}

核心就是利用僞元素,再生成一個圓,再添加相應的陰影即可,效果如下:

利用僞元素 ::after: 實現太陽的光暈及雲朵效果

注意!這裏是本文最爲關鍵的地方。如何利用剩下一個僞元素實現太陽的光暈及雲朵效果?

這裏就需要利用到 box-shadow 可以複製自身的技巧。在非常多篇的文章中也有反覆提到過。

譬如,當我們想實現一朵雲朵,像是這樣:

使用 box-shadow 即可輕鬆實現:

<div></div>
div{
  width:100px;
  height:100px;
  margin:50px auto;
  background:#999;
  border-radius:50%;
  box-shadow:
    120px 0px 0 -10px #999,
    95px 20px 0 0px #999,
    30px 30px 0 -10px #999,
    90px -20px 0 0px #999,
    40px -40px 0 0px #999;
}

通過動圖,感受一下是什麼意思:

嘿,這個雲朵不是和我們效果中的雲朵非常類似嗎?只需要調整一些位置,利用 overflow: hidden 裁剪掉多餘部分即可。

光圈其實也是同理,這裏,利用 ::after 僞元素,生成一個圓,利用多重 box-shadow,生成光暈和雲朵!

代碼如下:

&::after {
    content: "";
    position: absolute;
    width: 70px;
    height: 70px;
    inset: 10px;
    border-radius: 50%;
    box-shadow: 
        10px 60px 0 10px #fff,
        65px 60px 0 5px #fff,
        95px 70px 0 10px #fff,
        135px 45px 0 5px #fff,
        170px 35px 0 10px #fff,
        195px -5px 0 10px #fff,
        -10px 0 0 50px rgba(255, 255, 255, .2),
        15px 0 0 50px rgba(255, 255, 255, .15),
        40px 0 0 50px rgba(255, 255, 255, .21),
        10px 40px 0 10px #abc1d9,
        70px 35px 0 10px #abc1d9,
        95px 40px 0 10px #abc1d9,
        135px 20px 0 10px #abc1d9,
        155px 15px 0 10px #abc1d9,
        190px -20px 0 10px #abc1d9;
}

其核心,或者說費時間的地方在於調整每個 box-shadow 的位置和顏色,這樣,我們就得到了完整的日間效果圖:

夜間模式的實現

實現完日間效果,接下來,我們就需要實現夜間效果。其效果圖如下:

爲了實現最終的點擊切換,我們可以把夜間效果下,按鈕的樣式,寫在一個新的 class 內,這樣,後面只需要在點擊的過程中,去切換這個 class 即可。

<div class="active"></div>
div.active{
    ...
}

如上所示,我們接下來的工作就是尋找日間、夜間的差異點,將代碼填入上述的 div.active 即可。

首先,太陽變成了月亮,位置進行了移動,顏色進行了變化,並且月亮上多出了一些隕石坑,當然,其本質還是圓形。

這些修改都非常簡單,還是在原來的 ::before 基礎上修改即可:

div.active{
    &::before {
        translate: 130px;
        background: 
            radial-gradient(circle at 50% 20px, #939aa5, #939aa5 6.5px, transparent 7px, transparent),
            radial-gradient(circle at 35% 45px, #939aa5, #939aa5 11.5px, transparent 12px, transparent),
            radial-gradient(circle at 72% 50px, #939aa5, #939aa5 8.5px, transparent 9px, transparent),
            radial-gradient(#cbcdda, #cbcdda);
    }
}

這裏是非常好修改的,利用 radila-gradient(),也就是多重漸變,我們可以輕鬆的在一個元素內完成背景加上隕石坑的代碼:

繼續,夜間模式下,月亮也有光圈,代碼是可以複用的,並且夜間模式沒有了雲朵,取而代之是星星。

星星看起來有點複雜,我們待會處理,這裏僅僅需要把雲朵部分的陰影顏色,設置爲 transparent 即可。

div.active {
  &::after {
        transform: translate(130px);
        box-shadow: 
            10px 60px 0 10px transparent,
            65px 60px 0 5px transparent,
            95px 70px 0 10px transparent,
            135px 45px 0 5px transparent,
            170px 35px 0 10px transparent,
            195px -5px 0 10px transparent,
            10px 0 0 50px rgba(255, 255, 255, .2),
            -15px 0 0 50px rgba(255, 255, 255, .15),
            -40px 0 0 50px rgba(255, 255, 255, .21),
            10px 40px 0 10px transparent,
            70px 35px 0 10px transparent,
            95px 40px 0 10px transparent,
            135px 20px 0 10px transparent,
            155px 15px 0 10px transparent,
            190px -20px 0 10px transparent;
    }
}

效果如下:

爲什麼這裏不是去掉雲朵的代碼,而是把雲朵部分的陰影顏色,設置爲 transparent 呢?這樣做的原因是能夠在切換過程中,得到更好的動畫效果。

好!到這裏,只剩下夜間模式下的星星和背景了,背景是非常好解決的,主要是星星,看原效果的動圖,每一顆星星是帶有棱角的,而這種不規則圖案,確實是 CSS 最棘手的問題。

到這裏,無奈退而求其次,考慮使用小圓點模擬星星效果。(沒想到效果其實也很不錯!)

那這個問題又變成了和月亮與隕石坑類似的問題了,都是圓形,那就非常好解決。

最終,考慮在 div 本身的背景之上,設置一個大背景 background-size: 200% 100%,這樣,一半是日間的背景,一半是夜間的背景,在切換的過程中,只需要改變 background-position 即可。

這樣一來,代碼如下:

div {
    background: 
            radial-gradient(circle at 18% 20px, #fff, #fff 6px, transparent 7px, transparent),
            radial-gradient(circle at 35% 45px, #fff, #fff 1px, transparent 2px, transparent),
            radial-gradient(circle at 10% 70px, #fff, #fff 2.5px, transparent 3.5px, transparent),
            radial-gradient(circle at 25% 15px, #fff, #fff 3px, transparent 4px, transparent),
            radial-gradient(circle at 15% 50px, #fff, #fff 1.5px, transparent 2.5px, transparent),
            radial-gradient(circle at 30% 75px, #fff, #fff 5px, transparent 6px, transparent),
            radial-gradient(circle at 5% 30px, #fff, #fff 0.5px, transparent 1.5px, transparent),
            radial-gradient(circle at 25% 60px, #fff, #fff 0.5px, transparent 1.5px, transparent),
            radial-gradient(circle at 7% 35px, #fff, #fff 0.5px, transparent 1.5px, transparent),
            linear-gradient(90deg, #2b303e, #2b303e 50%, #5a81b4 50%, #5a81b4);
    background-repeat: no-repeat;
    background-size: 200% 100%;
    background-position: 100% 0;
}
div.active {
    background-position: 0 0;
}

這樣,夜間效果也就完美實現了:

添加過渡效果以及切換效果

最後,只需要加上一些過渡效果以及點擊切換時,元素樣式類名變化的 JavaScript 代碼即可。

完整的整個效果,代碼如下:

<div id="g-btn"></div>
body {
    background: #d9deea;
}
div {
    position: relative;
    width: 220px;
    height: 90px;
    background: 
            radial-gradient(circle at 18% 20px, #fff, #fff 6px, transparent 7px, transparent),
            radial-gradient(circle at 35% 45px, #fff, #fff 1px, transparent 2px, transparent),
            radial-gradient(circle at 10% 70px, #fff, #fff 2.5px, transparent 3.5px, transparent),
            radial-gradient(circle at 25% 15px, #fff, #fff 3px, transparent 4px, transparent),
            radial-gradient(circle at 15% 50px, #fff, #fff 1.5px, transparent 2.5px, transparent),
            radial-gradient(circle at 30% 75px, #fff, #fff 5px, transparent 6px, transparent),
            radial-gradient(circle at 5% 30px, #fff, #fff 0.5px, transparent 1.5px, transparent),
            radial-gradient(circle at 25% 60px, #fff, #fff 0.5px, transparent 1.5px, transparent),
            radial-gradient(circle at 7% 35px, #fff, #fff 0.5px, transparent 1.5px, transparent),
            linear-gradient(90deg, #2b303e, #2b303e 50%, #5a81b4 50%, #5a81b4);
    background-repeat: no-repeat;
    background-size: 200% 100%;
    background-position: 100% 0;
    border-radius: 90px;
    box-shadow: 
        0 -3px 4px #999,
        inset 0 3px 5px #333,
        0 4px 4px #ffe,
        inset 0 -3px 5px #ddd;
    cursor: pointer;
    overflow: hidden;
    transition: .5s all;
    &::before,
    &::after {
        content: "";
        position: absolute;
        transition: .5s all;
    }
    &::before {
        width: 75px;
        height: 75px;
        border-radius: 50%;
        background: #e9cb50;
        inset: 7.5px;
        box-shadow: 
            0 0 5px #333,
            inset 2px 2px 3px #f8f4e4,
            inset -2px -2px 3px #665613;
        z-index: 1;
    }
    &::after {
        width: 70px;
        height: 70px;
        inset: 10px;
        border-radius: 50%;
        box-shadow: 
            10px 60px 0 10px #fff,
            65px 60px 0 5px #fff,
            95px 70px 0 10px #fff,
            135px 45px 0 5px #fff,
            170px 35px 0 10px #fff,
            195px -5px 0 10px #fff,
            -10px 0 0 50px rgba(255, 255, 255, .2),
            15px 0 0 50px rgba(255, 255, 255, .15),
            40px 0 0 50px rgba(255, 255, 255, .21),
            10px 40px 0 10px #abc1d9,
            70px 35px 0 10px #abc1d9,
            95px 40px 0 10px #abc1d9,
            135px 20px 0 10px #abc1d9,
            155px 15px 0 10px #abc1d9,
            190px -20px 0 10px #abc1d9;
    }
}
div:hover::before {
    filter: contrast(90%) brightness(110%);
    scale: 1.05;
}
div.active {
    background-position: 0 0;
    
    &::before {
        translate: 130px;
        background: 
            radial-gradient(circle at 50% 20px, #939aa5, #939aa5 6.5px, transparent 7px, transparent),
            radial-gradient(circle at 35% 45px, #939aa5, #939aa5 11.5px, transparent 12px, transparent),
            radial-gradient(circle at 72% 50px, #939aa5, #939aa5 8.5px, transparent 9px, transparent),
            radial-gradient(#cbcdda, #cbcdda);
    }
    &::after {
        transform: translate(130px);
        box-shadow: 
            10px 60px 0 10px transparent,
            65px 60px 0 5px transparent,
            95px 70px 0 10px transparent,
            135px 45px 0 5px transparent,
            170px 35px 0 10px transparent,
            195px -5px 0 10px transparent,
            10px 0 0 50px rgba(255, 255, 255, .2),
            -15px 0 0 50px rgba(255, 255, 255, .15),
            -40px 0 0 50px rgba(255, 255, 255, .21),
            10px 40px 0 10px transparent,
            70px 35px 0 10px transparent,
            95px 40px 0 10px transparent,
            135px 20px 0 10px transparent,
            155px 15px 0 10px transparent,
            190px -20px 0 10px transparent;
    }
}
const btn = document.querySelector('#g-btn');
btn.addEventListener('click', (e) => {
    btn.setAttribute('class', btn.getAttribute("class") === "active" ? "" : "active");
});

來看看最終效果:

是不是基本上還原了原效果?這裏我們僅僅使用了一個標籤,核心配合了 box-shadow 以及背景漸變完成了整個按鈕效果。

完整的代碼,你可以戳這裏 CodePen Demo -- Single Div BTN Toggle Effect

總結一下

好了,本文到此結束,希望對你有幫助 😃

更多精彩 CSS 技術文章彙總在我的 Github -- iCSS ,持續更新,歡迎點個 star 訂閱收藏。

如果還有什麼疑問或者建議,可以多多交流,原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。

最後,我的小冊 《CSS 技術揭祕與實戰通關》上線了,想了解更多有趣、進階、系統化的 CSS 內容,可以猛擊 - LINK

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章