未完待續
現在css3愈來愈發達,focus-within等屬性也已經開始在Chrome獲得支持。若是有出色的css功底,一點點ps技能,你也能用css3配合原生html標籤寫出優秀的框架。經過對css3的實踐,我發現自定義原生控件並非什麼難事,因而小試牛刀寫了個 純CSS3實現Material Design。javascript
關於本文的全部代碼實現,以及更多純css控件,請在 https://github.com/Seasonley/... 中查看。
本文針對本人開發的純css框架Sultana中重要的幾個經常使用組件進行深刻分析,與你們探討如何一步步構思實現,包括:css
衆所周知Bootstrap柵格系統 提供了不一樣設備寬度下的12柵格,那麼咱們如何戰勝Bootstrap,用css3變量實現一個強大的自適應柵格呢?html
假設咱們考慮4種設備,以下方表格所示。仿照Bootstrap咱們將他們命名爲sm,md,lg。前端
su-col | 特小屏(-,480px) | 小屏(480px,720px) | 中屏(720px,1200px) | 大屏(1200px,+) | 固定數值(覆蓋以上變量) |
css變量名 | --sm |
--md |
--lg |
--col |
|
類型 | int (1 , 2 , ... , 12) |
int (1 , 2 , ... , 12) |
int (1 , 2 , ... , 12) |
number (90% , 0.3 , ...) |
那麼col
幹什麼用?最終容器的寬度應該轉換成單一變量,前面3者都是輔助。假設咱們的佈局以下html5
<div su-row> <div su="primary" su-col style="--sm:3;--md:4;--lg:12;--sm-v:hidden"></div> <div su="light" su-col style="--sm:3.2;--md:4.5;--lg:12;"></div> <div su="info" su-col style="--sm:3.3;--lg:12;"></div> </div> <div su-row> <div su="warning" su-col style="--col:0.22;--sm-d:none"></div> <div su="info" su-col style="--col:0.33;"></div> <div su="primary" su-col style="--col:0.44;"></div> </div>
咱們但願不一樣設備的col寬度按照變量自適應,還能夠有display
和visiblity
這些輔助操做,接下來看css實現,以-md
爲例子,代碼以下。java
@media screen and (min-width: 721px) { div[su-col] { --col: calc(var(--md) / 12); display: var(--md-d); visibility: var(--md-v); } }
接着只要在su-col中使用var(--col)就能搞定自適應。代碼以下。jquery
div[su-col] { width: calc(100% * var(--col)); }
是否是很輕鬆,每一行都是精華。若是直接用--col
,那適配變量失效,這一般用在不須要適配的場景下。android
flex佈局逐漸代替浮動佈局,那麼自定義一些簡寫的class來讓代碼看起來更整潔也是很是有必要的。本人提供一個實現方式,並不必定受歡迎,不感興趣能夠跳過。css3
flex屬性縮寫參照:git
html示例
看這個標題就祕製重量級,下拉控件很差辦,不少css框架都只能div套啊套的,再用js搞點數據綁定。不過既然有focus-within
。咱們先腦補一下,點擊這個a標籤,a+div
裏的input[radio]
都顯示,不focus
則隱藏。這解決了下拉動畫效果。那麼鼠標通過要高亮,input
又不能寫文字,也沒有僞元素能夠弄dataset
,那隻好input+span
組合一下了。假設html部分代碼以下。
<button su="outlined -"> <a href="javascript:">(blank)</a> <ul> <input checked type="radio" name="ra" value=""/> <span>(blank)</span> <input title="A.dada" type="radio" name="ra" value="1"/> <span>A.da</span> <input title="B.huha" type="radio" name="ra" value="2"/> <span >B.la</span> </ul> </button>
這樣看來彷佛能寫個導航欄了呢,多個按鈕拼一下什麼的。
如標題,接下來解決選中項的顯示,很簡單,absolute絕對定位嘛。
button[su~="-"] input:checked+span { position: absolute; top: 0px; }
默認來個value=""的,文字爲(blank),算是很尷尬的委屈求全了,缺點是選中項再也不在下拉列表中顯示了,其次文字是不能點擊選中的,只能點空白處。
有意思,看完上面的奇葩實現select,你應該對這個更感興趣一點。做爲一個前端你不會本身寫日期控件,你好意思嗎你?成天用jquery-datepicker你的良心不會痛嗎?這回咱們再尬一回,用css和少許的js代碼來實現。尬在哪呢?31個input[radio],誰會用啊,可能只有那些禁用js的瀏覽器的需求方須要這個。
oninput的時候須要算出閏年+這個月1號星期幾,把它放到dataset,由css枚舉1號的7種星期狀況,css枚舉大月小月和閏年2月。(這行描述最重要)
代碼實現以下
html
<form oninput="ymd.value=y.value+'-'+('0'+m.value).slice(-2)+'-'+d.value;ymd.dataset.leap=(y.value % 4 == 0) && (y.value % 100 != 0 || y.value % 400 == 0);ymd.dataset.day=new Date(y.value+'-'+('0'+m.value).slice(-2)+'-1').getDay()" onclick="ymd.value=y.value+'-'+('0'+m.value).slice(-2)+'-'+d.value" onmouseover="ymd.dataset.day=new Date(y.value+'-'+('0'+m.value).slice(-2)+'-1').getDay()"> <label su-date> <input su type="date" name="ymd" readonly> <div> <input name="y" type="number" min="1970" max="3000" value="2000" onchange="this.setAttribute('value', this.value)"> <input name="m" type="number" min="1" max="12" value="1" onchange="this.setAttribute('value', this.value)"> <div> <input name="d" type="radio" value="01" checked> <input name="d" type="radio" value="02"> ...
css
label[su-date] input[type="number"][value="2"]+div input[type="radio"]:last-of-type, label[su-date] input[type="number"][value="4"]+div input[type="radio"]:last-of-type, label[su-date] input[type="number"][value="6"]+div input[type="radio"]:last-of-type, label[su-date] input[type="number"][value="9"]+div input[type="radio"]:last-of-type, label[su-date] input[type="number"][value="11"]+div input[type="radio"]:last-of-type, label[su-date] input[type="number"][value="2"]+div input[type="radio"][value="30"], label[su-date] input[type="number"][value="2"]+div input[type="radio"][value="29"] { display: none } label[su-date] input[name="ymd"][data-leap="true"]+div input[type="radio"][value="29"] { display: block } label[su-date] input[name="ymd"][data-day="1"]+div input[type="radio"][value="01"] { margin-left: calc(0 * var(--x2)) } label[su-date] input[name="ymd"][data-day="2"]+div input[type="radio"][value="01"] { margin-left: calc(1 * var(--x2)) } label[su-date] input[name="ymd"][data-day="3"]+div input[type="radio"][value="01"] { margin-left: calc(2 * var(--x2)) } label[su-date] input[name="ymd"][data-day="4"]+div input[type="radio"][value="01"] { margin-left: calc(3 * var(--x2)) } label[su-date] input[name="ymd"][data-day="5"]+div input[type="radio"][value="01"] { margin-left: calc(4 * var(--x2)) } label[su-date] input[name="ymd"][data-day="6"]+div input[type="radio"][value="01"] { margin-left: calc(5 * var(--x2)) } label[su-date] input[name="ymd"][data-day="0"]+div input[type="radio"][value="01"] { margin-left: calc(6 * var(--x2)) }
缺點:老長老長的html和css...
預覽:
在掘金上看到網易考拉前端寫的CSS Scroll Indicator —— 純CSS 滾動指示器,實現方式很巧妙,經過直角三角形背景的頂部界面與頁面高度相關性製造一個實際上滾背景,看起來在伸縮進度條的效果。該文提到已知的2個缺點。
background-size: 100% calc(100% - 99vh);
中的99vh
是相對值,如果視窗高度比較小,進度條會填不滿進度條槽(可考慮加min-height
來弱化)那它放到實際項目中效果如何呢,本人試驗了一下,在篇幅很長,標籤元素嵌套不少的頁面中加入scroll_indicator,滾動過程當中背景重繪十分卡,以致於元素按鈕點開都不能及時響應。因此若是是純文本博客或者說明文檔之類的元素標籤和css足夠少的狀況下,可使用該方式實現靜態文檔的進度條,複雜的dom和css狀況下不建議使用。當本人放到本身項目中測試時,按住滾動條上下快速拖動時,滾動條都是跳幀的。。。
Bootstrap輪播控件經過js實現,那麼css能實現嗎,顯然是能夠的。實現到什麼程度呢?本人枚舉如下能夠實現功能:
看上去很全了,本人講一下具體思路。
輪播須要假設在absolute
的畫布上橫向滾動,改變元素的水平位移數值margin-left
來實現。經過css關鍵幀動畫能完成。以下:
@keyframes rua { 0%,20% {margin-left: 0} 25%,40% {margin-left: calc(0px - 1*var(--w))} 45%,60% {margin-left: calc(0px - 2*var(--w))} 65%,80% {margin-left: calc(0px - 3*var(--w))} 85%,100%{margin-left: calc(0px - 4*var(--w))} }
指示器能夠同新標籤,但不能像標題元素包裹在圖片容器內,由於指示器是不能滾動的。因而絕對定位相對於整個控件容器內就能夠。html代碼以下:
<aside su="." style="--w:400px;--h:300px;--p:20px;"> <button></button><button></button> <button></button><button></button> <button></button> <ul> <li style="background:gray;"><a>標題1</a></li> <li style="background:orange;"><a>標題2</a></li> <li style="background:teal;"><a>標題3</a></li> <li style="background:blue;"><a>標題4</a></li> <li style="background:black;"><a>標題5</a></li> </ul> </aside>
在上述5點功能中,
<1><3>能夠用css變量解決,好比--w:400px;--h:300px;--p:20px;
實現了寬度400px
,高度300px
,圓點大小直徑20px
的控件。
<2>單獨設a
標籤
<4>用focus-within
實現點擊後css動畫暫停在某個關鍵幀,缺點一是:有幾個圖就要寫幾個動畫,缺點二是:點擊後雖然選中了,但鼠標移出控件不會繼續滾動,在控件外點擊會跳回初始動畫關鍵幀。以一個圖的動畫爲例:
aside[su~="."] button:nth-child(5):focus-within~ul { animation: ma5 .5s ease-out forwards; } @keyframes ma5 { 100% {margin-left: calc(-4 * var(--w))} }
<5>經過hover
的暫停css動畫來控制
aside[su~="."] button:hover, aside[su~="."] ul:hover { will-change: transform; animation-play-state: paused; }
總體效果還不錯:
進階方案 css-scroll-snap
在沒出現focus-within
以前,用臨近元素選擇器+css能實現純css的下拉選項的導航欄,在這裏就很少展開了。最後一個focus-within
的魔法魅力。咱們須要實現相似android側邊欄滑出的效果。
網上已經有人實現了《CSS :focus-within》 Airen的博客,那我就講一下他是如何實現的。該文中用到了移動端延伸出的冷門css屬性touch-action: manipulation
。CSS屬性 touch-action
用於指定某個給定的區域是否容許用戶操做,以及如何響應用戶操做(好比瀏覽器自帶的划動、縮放等)(MDN)。移動端300ms延遲,就可使用 touch-action: manipulation;
來解決。咱們只須要實現點擊左上角按鈕後滑出菜單,聲明一下按鈕點擊不影響其手勢操做就好了。
本控件只要實現focus-within
後動畫彈出菜單。失去焦點的時候彈回去就好了。其實和touch-action沒什麼關係。先定義菜單部分的css,將其扔到左邊屏幕外(margin
或者transform
都行):
#nav-content transform: translateX(-100%); transition: transform .3s;
再定義選中後的菜單:
#nav-container:focus-within #nav-content { transform: none; }
大體思路就是這樣。講了那麼多focus-within
的相關內容,但國內部分webkit內核瀏覽器還在50如下的階段,還請各位使用chrome63以上版本訪問。
先來看看 bulma 是怎麼寫的
ul li p a
相互嵌套,沒錯是個很好的實現方式,若是加focus-within
和相鄰元素選擇也能實現點擊後的動畫效果,不過要稍微改變一下嵌套方式,但這不是本文的討論重點。focus-within
有它的缺點,對於一個菜單來講,用戶好不容易展開找到了,一點別的菜單層級,以前的全縮回去了,那怎麼行。必需要有一個展開後點擊再縮回去的功能。
這就要引出神奇的summary
標籤,只用配合details
用時,當details
內的summary
元素被點擊時,details所有顯示,再次點擊縮起只剩summary
。在張鑫旭大神的藉助HTML5 details
,summary
無JS實現各類交互效果中提到了不少可實現的控件,樹,菜單等等。
本人仿照Bulma用summary
特性實現了一個帶動畫的導航菜單,代碼還算優雅,以下圖所示。
一個原生的範圍輸入控件,在瀏覽器是十分簡陋的,因此並木有人想去用原生的。咱們看一下螞蟻金服前端團隊是怎麼實現的(ant.desgin):
貌似是section>ul>li
,再加class表示zero
full
的小星星,移上去還有動畫,又是一堆class。咱們先不考慮動畫的實現,先來吐槽這個dom多了以後渲染class的性能問題,li裏面又有2個半的小星星來支持step=0.5
的打分,這些原生瀏覽器都支持的屬性何須用2+5*3=17個dom元素生成?光說不練假把式,趕忙拉出個代碼來溜溜。
首先是html,就一行~
<input su="star" value="1" min="1" max="5" type="range" onchange="this.setAttribute('value', this.value)" />
其次是css:
input[type="range"][su~="star"] { cursor: pointer;width: calc(16px * 5);height: 16px; -webkit-appearance: none;-webkit-mask-image: var(--star); background: linear-gradient(to right, var(--Primary), var(--Primary)), linear-gradient(to right, var(--Gray), var(--Gray)); background-repeat: no-repeat; } input[type="range"][su~="star"][value="1"] { background-size: calc(16px * 1) 16px, calc(16px * 5) 16px; } input[type="range"][su~="star"][value="2"] { background-size: calc(16px * 2) 16px, calc(16px * 5) 16px; } ...
經過不一樣屬性的input背景,將小星星呈現出不一樣的顏色,linear-gradient
渲染出進度,若是是半星也好辦,在step=0.5
的狀況下多枚舉幾個.5的分數狀況就行。效果以下:
進階:表開發注意事項
transform
在上文的實現android滑動菜單有提到,再擴展一下它的其餘用途。咱們能夠用來寫tooltip。對於一個title
屬性的標籤,衆所周知鼠標懸浮會顯示一個方框,內容是title
屬性的值。那麼僞元素的content: attr
剛好能配合transform
構造不一樣方位的tooltip。
[su-hint~="bottom"]:after { top: 100%; left: 50%; transform: translateX(-50%); } [su-hint~="bottom"]:hover:after { transform: translateX(-50%) translateY(8px); } [su-hint~="right"]:after { margin-bottom: -14px; left: 100%; bottom: 50%; } [su-hint~="right"]:hover:after { transform: translateX(8px); }
github上早有強大的自定義tooltip了,若是你願意犧牲before
after
2個僞元素實現tooltip的氣泡和箭頭,建議使用hint.css