離譜的 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
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...