離譜的 CSS!從表盤刻度到藝術剪紙

来源:https://www.cnblogs.com/coco1s/archive/2022/05/05/16223977.html
-Advertisement-
Play Games

某日,群里有這樣一個問題,如何實現這樣的表盤刻度: 這其實是個挺有意思的問題,方法也有很多。 單標簽,使用 conic-gradient 實現表盤刻度 最簡單便捷的方式,就是利用角向漸變的方式 conic-gradient,代碼也非常簡單,首先,我們實現一個重覆角向漸變: <div></div> d ...


某日,群里有這樣一個問題,如何實現這樣的表盤刻度:

這其實是個挺有意思的問題,方法也有很多。

單標簽,使用 conic-gradient 實現表盤刻度

最簡單便捷的方式,就是利用角向漸變的方式 conic-gradient,代碼也非常簡單,首先,我們實現一個重覆角向漸變:

<div></div>
div {
    width: 300px;
    height: 300px;
    border-radius: 50%;
    background: repeating-conic-gradient(
        #000 0, #000 .8deg, transparent 1deg, transparent calc((360 / 60) * 1deg)
    );
}

其實比較難理解的是 calc((360 / 60) * 1deg),這是因為表盤一共通常有 60 個刻度。效果大概是這樣:

接下來,只需要將中間鏤空即可。如果背景色是白色,直接疊加一個圓形即可,當然,更好的方式是通過 mask 屬性進行鏤空:

{
    background: repeating-conic-gradient(
        #000 0, #000 .8deg, transparent 1deg, transparent calc(360 / 60 * 1deg)
    );
    mask: radial-gradient(transparent 0, transparent 140px, #000 140px)
}

這樣,我們就得到了一個表盤刻度:

這是使用一個標簽就能實現的方式,當然,缺點也很明顯:

  1. 鋸齒感嚴重,漸變的通病
  2. 由於是使用的角向漸變,刻度存在頭重腳輕的現象,越向內部,寬度越窄(刻度愈大,差異愈加明顯)

使用多個標簽實現

如果不介意使用太多的標簽,那麼通常而言,更容易想到的方法就是利用 60 個標簽,加上旋轉實現:

<div class="g-container">
        <div class="g-item"></div>
        // ... 一共 60 個
        <div class="g-item"></div>
    </div>
.g-item {
    position: absolute;
    width: 4px;
    height: 12px;
    background: #000;
    left: 0;
    top: 0;
    transform-origin: 0 150px;
}
@for $i from 1 through 60 { 
    .g-item:nth-child(#{$i}) {
        transform: rotate(#{($i - 1) * 6deg});
    }
}

像是這樣,我們通過 60 個 div 標簽,利用 SASS 的 for 語法減少重覆的代碼量,批量實現每個元素逐漸繞一點旋轉一定的角度,也是可以實現一個表盤刻度的:

這個方案的好處是,每個刻度粗細一致,並且,不會產生鋸齒。

藉助 -webkit-box-reflect 減少標簽數

當然,上述方案的缺點就在於,使用了 60 個標簽去完成這樣一個簡單的圖形,有點太奢侈了。

我們希望儘可能的優化優化標簽的個數。此時,我們很容易的想到 -- -webkit-box-reflect,倒影效果屬性。

-webkit-box-reflect 是一個非常有意思的屬性,它讓 CSS 有能力像鏡子一樣,反射我們元素原本繪製的內容。

-webkit-box-reflect 的語法非常簡單,最基本的用法像是這樣:

div {
    -webkit-box-reflect: below;
}

其中,below 可以是 below | above | left | right 代表下上左右,也就是有 4 個方向可以選。

假設我們有如下一張圖片:

<div></div>
div {
    background-image: url('https://images.pokemontcg.io/xy2/12_hires.png');
}

加上 -webkit-box-reflect: right,也就是右側的倒影:

div {
    background-image: url('https://images.pokemontcg.io/xy2/12_hires.png');
    -webkit-box-reflect: right;
}

效果如下,生成了一個元素右側的鏡像元素:

藉助 -webkit-box-reflect: right,我們至少可以從 60 個標簽減少到 15 個標簽的使用。簡單的嵌套兩層即可。

我們簡單改變一下 HTML 結構:

<div class="g-parent">
    <div class="g-container">
        <div class="g-item"></div>
        // ... 一共 16 個
        <div class="g-item"></div>
    </div>
</div>

這一次,我們只需要實現 1/4 圓的刻度即可:

@for $i from 1 through 16 { 
    .g-item:nth-child(#{$i}) {
        transform: rotate(#{($i - 1) * 6deg});
    }
}

我們可以得到這樣一個圖形:

基於這個圖形,我們只需要先向左側倒影一次,再向下倒影一次即可:

.g-container {
    -webkit-box-reflect: below;
}
.g-parent {
    -webkit-box-reflect: left;
}

效果如下:

大致的效果就出來了,當然,0點、3點、6點、9點上左下右 4 個刻度有點問題。不過 -webkit-box-reflect 也提供距離調整功能,再簡單修改下代碼:

.g-container {
    -webkit-box-reflect: below 4px;
}
.g-parent {
    -webkit-box-reflect: left -4px;
}

這次,效果就是我們想要的最終效果:

我們就成功地藉助 -webkit-box-reflect 節約了 3/4 的標簽數量。完整的代碼:CodePen Demo -- Clock ticks

-webkit-box-reflect 與剪紙藝術

到這裡,我不由得想到,這種對折、再對折,鏡像再鏡像的方式,與我們小時候的摺紙藝術非常的類似

那麼,基於這樣一個模板:

<div class="g-parent">
    <div class="g-container">
        <div class="g-item"></div>
    </div>
</div>
.g-container {
    -webkit-box-reflect: below;
}
.g-parent {
    -webkit-box-reflect: left;
}

我只需要繪製 .g-item 裡面的內容,將他通過 2 次 -webkit-box-reflect 鏡像,就能得到一個剪紙圖形

而如何得到隨機有意思的不規則圖形呢?

clip-path 是個很不錯的選擇,我們通過 clip-path 隨機對一個矩形進行裁剪:

.g-item {
    width: 150px;
    height: 150px;
    background: #000;
    clip-path: polygon(25% 0%,71% 66%,59% 0%,79% 23%,95% 4%,100% 40%,77% 100%,38% 100%,47% 71%,36% 30%,23% 60%,0% 100%,5% 37%);
}

效果如下:

經過兩次鏡像後的效果如下:

是不是有那麼點意思了?可以隨機利用 clip-path 多嘗試幾次,可以得到不同的效果:

CodePen Demo -- Pure CSS Page Cutting

-webkit-box-reflect 配合 clip-path 配合 mask

但是上面的圖形看著還是太簡單了,幾個原因,一是對折的次數和角度不夠,缺少對折次數和不同角度的對折,二是圖形不夠負責。

我又想起了之前看到過的一篇類似的剪紙相關的文章 -- Paper Snowflakes: Combining Clipping and Masking in CSS

再上述的基礎上,還使用了 mask,將圖形切割的更細。

我們再來一次,還是同樣的結構,當然,為了得到更負責的圖形,我們設置了 4 個 .g-item

<div class="g-parent">
    <div class="g-container">
        <div class="g-item"></div>
        <div class="g-item"></div>
        <div class="g-item"></div>
        <div class="g-item"></div>
    </div>
</div>

首先,還是設置一個 clip-path 切割後的圖形:

.g-item:nth-child(1) {
    width: 150px;
    height: 150px;
    background: #000;
    clip-path: polygon(17% 41%,6% 39%,16% 91%,18% 78%,56% 11%,28% 71%,99% 67%,25% 65%,69% 72%,46% 28%,90% 76%,67% 34%,48% 30%,79% 36%,59% 15%,23% 92%,16% 1%,32% 81%,72% 38%,50% 59%,71% 98%,66% 87%,83% 14%,36% 71%,49% 7%,9% 25%,52% 76%,10% 83%,17% 41%);
}

效果如下:

這個圖可能它會特別的奇怪,沒有問題,我們繼續。

如果,我們將一個矩形,從左下角開始算,分為 4 份,那麼每一個份就是 90° / 4 = 22.5°,我們希望切割得到其中一份:

image

我們可以藉助 mask 去完成這個切割:

.g-item:nth-child(1) {
    width: 150px;
    height: 150px;
    background: #000;
    clip-path: polygon(.....);
    mask: conic-gradient(from 0turn at 0 100%, #000, #000 22.5deg, transparent 22.5deg, transparent);
}

上述的圖形,就被切割成了這樣:

OK,基於此,我們可以得到同樣的第二份圖形,但是我們給它加一個 rotateY(180deg)

.g-item:nth-child(2) {
    width: 150px;
    height: 150px;
    background: #000;
    clip-path: polygon(.....);
    mask: conic-gradient(from 0turn at 0 100%, #000, #000 22.5deg, transparent 22.5deg, transparent);
    transform: rotateY(180deg);
}

效果如下:

我們再通過 rotateZ(),給第二圖形旋轉一定的角度,讓他和第一個貼合在一起:

.g-item:nth-child(2) {
    clip-path: polygon(.....);
    mask: conic-gradient(from 0turn at 0 100%, #000, #000 22.5deg, transparent 22.5deg, transparent);
    transform: rotateY(180deg) rotateZ(-45deg);
}

就得到了一個斜向角度的鏡像圖像:

因為 .g-item 被切割成了 4 份,所以第 3、4 個圖形如法炮製即可,這樣,整個 .g-item 的效果如下:

再打開 -webkit-box-reflect,整個的圖形效果如下:

這樣,一個剪紙圖形就誕生啦!

當然,為了得到不一樣的效果,我們可以藉助 JavaScript 去隨機生成 CSS 中的各類參數,完整的代碼,大概是這樣:

<div class="g-parent">
    <div class="g-container">
        <div class="g-item"></div>
        <div class="g-item"></div>
        <div class="g-item"></div>
        <div class="g-item"></div>
    </div>
</div>
.g-container,
.g-parent {
    position: relative;
    display: flex;
    width: 150px;
    height: 150px;
}
.g-item {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    background: #000;
    transform-origin: 0 100%;
    clip-path: var(--polygon, polygon(40% 0%,0% 91%,52% 100%,0% 37%,77% 23%,77% 76%,43% 22%,55% 88%,100% 100%,100% 10%));
}
.g-item {
    mask: conic-gradient(from 0turn at 0 100%, #000, #000 22.5deg, transparent 22.5deg, transparent);
}
@for $i from 1 through 5 { 
    .g-item:nth-child(#{$i}) {
        transform: rotateZ(calc(22.5deg * #{$i - 1}));
    }
}
.g-item:nth-child(2) {
    transform: rotateY(180deg) rotateZ(-60deg);
}
.g-item:nth-child(4) {
    transform: rotateY(180deg) rotateZ(-105deg);
}
.g-container {
    -webkit-box-reflect: below;
}
.g-parent {
    -webkit-box-reflect: left;
}
const ele = document.querySelectorAll('.g-item');

document.addEventListener('click', function(e) {
    let num = Math.floor(Math.random() * 30 + 10);
    
    const maskR =  Math.floor(Math.random() * 22.5 + 22.5 ) + 'deg';
    const r1 = Math.floor(Math.random() * 100) + '%';
    const r2 = Math.floor(Math.random() * 100) + '%'; 
    
    let polygon = 'polygon(' + r1 + ' ' + r2 + ',';
    
    for (let i=0; i<num; i++) {
         const newR1 = Math.floor(Math.random() * 100) + '%';
         const newR2 = Math.floor(Math.random() * 100) + '%';

        polygon += newR1 + ' ' + newR2 + ','
    }
    
    polygon += r1 + ' ' + r2 + ')';
    
    [...ele].forEach(item => {
        item.setAttribute('style', `--polygon:${polygon};-webkit-mask:conic-gradient(from 0turn at 0 100%, #000, #000 ${maskR}, transparent ${maskR}, transparent)`);
    });
});

這樣,每次點擊滑鼠,我們都能得到不同的隨機剪紙圖案:

看看這個簡單錄製的 GIF:

完整的代碼,你可以猛擊這裡 CodePen Demo -- Pure CSS Art Page Cutting

最後

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

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • App 性能優化 RecyclerView 1、onBindViewHolder 運行在 UI 線程,不宜進行邏輯等耗時操作,只適合把數據填入視圖; 2、使用 support 包下麵的 DiffUtil 局部刷新處理,DiffUtil(內部也是調用局部刷新方法)可以對比數據的差異,是否更改; Dif ...
  • OpenHarmony 社區的繁榮離不開合作伙伴的支持和貢獻,相容性工作組作為社區的一部分,我們將不斷優化測試用例和測評流程,為眾多提交測評的廠家提供支持。 ...
  • 原文地址:Android8.0 後臺服務保活的一種思路 | Stars-One的雜貨小窩 項目中有個MQ服務,需要一直連著,接收到消息會發送語音,且手機要在鎖屏也要實現此功能 目前是使用廣播機制實現,每次MQ收到消息,觸發一次啟動服務操作邏輯 在Android11版本測試成功,可實現上述功能 步驟 ...
  • 前言 本篇是以 HarmonyOS 官網的基於 TS 擴展的聲明式開發範式文檔,頁面佈局與連接為基礎進行編寫。儘管原篇已非常精簡,但是作為初學者想要快速入門,使用 ets 方式實現出 List 佈局、Grid 佈局、數據連接及頁面跳轉功能,還是稍顯晦澀。所以筆者將原文進行整合,提取出其中的要點,以便 ...
  • 隨著市場愈發成熟,開發者從平衡收益和風險的角度開始逐步探索混合變現的優勢,內購+廣告就是目前市場上混合變現的主要方式之一。 對於混合變現模式,您是否有這樣的困惑: 如何判斷哪些用戶更願意看廣告、哪些用戶付費意願更高,更好地平衡內購和廣告? 在提升整體收入的基礎上,怎樣為用戶提供更好的產品體驗? HM ...
  • 問題描述: 工作中碰到這樣一種場景, WebApp 已經實現了IM即時通訊及基於WebRTC實現的音視頻會議,音視頻聊天。 也是半路接手的項目,項目整體是使用WKWebView套殼載入h5 頁面實現(後期過審還有很多路要走) 。 h5與原生交互的方案使用的javascriptCore(具體如何使用, ...
  • 今天的內容vue腳手架,越來越有內味了,也慢慢地開始有點難度了哈哈,但是沒有關係,慢慢學慢慢琢磨,我倒是感覺有點越來越像node了,不知道怎麼回事,這是要向後端發展的節奏啊 一.初始化Vue腳手架 1.說明 一般腳手架選擇最新版本 2.具體步驟 全局安裝vue/cli腳手架 切換到項目目錄,運行 v ...
  • 常用函數封裝 獲取某日期若幹個工作日後的日期 * 參數: * time: [String] 給定日期 yyyy-MM-dd * itervalByDay: [Number] 相隔工作日 * separator: [String] 年月日分隔符 * 返回: * rq:[String] 匹配的日期yyy ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...