本文轉載自【微信公眾號:趣談前端,ID:beautifulFront】經微信公眾號授權轉載,如需轉載與原文作者聯繫
前言
作為一個前端框架的重度使用者,在技術選型上也會非常注意其生態和完整性.筆者先後開發過基於vue,react,angular等框架的項目,碧如vue生態的elementUI, ant-design-vue, iView等成熟的UI框架, react生態的ant-design, materialUI等,這些第三方UI框架極大的降低了我們開發一個項目的成本和複雜度,使開發者更專注於實現業務邏輯和服務化.
但隨著對用戶體驗的越來越重視,對交互體驗要求的提高以及css3等新標準的出現,使得web更加大放異彩, 各種動效的實現都變得非常容易.筆者在研究materialUI框架時對於它的交互及其讚嘆.所以為了自己能實現一個類似materialUI的按鈕點擊動畫,並封裝到自己的UI庫中,筆者特地總結了一些思路,希望可以和廣大的前端工程師們一起探討.
正文
首先我們看一下materialUI的按鈕點擊效果:
本質上也是用了css3動畫的特性, 筆者查看原始碼和通過點擊發現materialUI會根據點擊位置不同而作不同位置的動畫,這個有點意思.我們先不講這麼複雜的例子,下面通過css3的方案來實現一個類似的效果.筆者實現的效果如下:
上圖已經是筆者基於react封裝好的一個按鈕Button組件,那麼我們就先一步步實現它吧.
1. 原理
這個動效的原理其實也很簡單,就是利用css3的transition過渡動畫,配合::after偽對象就可以實現,點擊的時候由於元素會激活:active偽類, 然後我們基於這個偽類, 在::after偽對象上做背景的動畫即可. 偽代碼如下:
.xButton {
position: relative;
overflow: hidden;
display: inline-block;
padding: 6px 1em;
border-radius: 4px;
color: #fff;
background-color: #000;
user-select:none; // 禁止用戶選中
cursor: pointer;
}
.ripple {
&::after {
content: "";
display: block;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background-image: radial-gradient(circle, #fff 10%, transparent 11%);
background-repeat: no-repeat;
background-position: 50%;
transform: scale(12, 12);
opacity: 0;
transition: transform .6s cubic-bezier(.75,.23,.43,.82), opacity .6s;
}
&:active::after {
transform: scale(0, 0);
opacity: .5;
}
}
複製代碼
以上代碼就是通過設置transform的scale以及透明度, 並且設置一個漸變的徑向背景圖像來實現水波紋動畫的為了實現更優雅的動畫,上面的css動畫的實現可以藉助cubic-bezier這個在線工具,他可以生成各種不同形式的貝塞爾曲線.工具長這樣:
2. 組件設計思路
僅僅用上述代碼雖然可以實現一個按鈕點擊的動畫效果,但是並不通用, 也不符合作為一個經驗豐富的程式設計師的風格,所以接下來我們要一步步把它封裝成一個通用的按鈕組件,讓它無所不用.
組件的設計思路我這裡參考ant-design的模式, 基於開閉原則,我們知道一個可擴展的按鈕組件一般都具備如下特點:
允許用戶修改按鈕樣式對外暴露按鈕事件方法提供按鈕主題和外形配置可插拔,可組合 基於以上幾點,我們來設計這個react組件.3. 基於react和css3的button組件具體實現
首先,我們的組件是採用react實現, 技術點我會採用比較流行的umi腳手架, classnames庫以及css Module, 代碼很簡單, 我們來看看吧.
import classnames from 'classnames'
import styles from './index.less'
/**
* @param {onClick} func 對外暴露的點擊事件
* @param {className} string 自定義類名
* @param {type} string 按鈕類型 primary | warning | info | default | pure
* @param {shape} string 按鈕形狀 circle | radius(默認)
* @param {block} boolean 按鈕展示 true | false(默認)
*/
export default function Button(props) {
let { children, onClick, className, type, shape, block } = props
return <div
className={classnames(styles.xButton, styles.ripple, styles[type], styles[shape], block ? styles.block : '', className)}
onClick={onClick}
>
{ children }
</div>
}
複製代碼
這是button的js部分,也是組件設計的核心, 按鈕組件對外暴露了onClick, className, type, shape, block這幾個props, className用於修改組件類名以便控制組件樣式, type主要是控制組件的風格, 類似於antd的primary等樣式, shape用來控制是否是圓形按鈕還是圓角按鈕, block用來控制按鈕是否是塊.具體形式如下:
經過優化後的css長這樣:
.xButton {
box-sizing: border-box;
display: inline-block;
padding: 6px 1em;
border-radius: 4px;
color: #fff;
font-family: inherit;
background-color: #000;
user-select:none; // 禁止用戶選中
cursor: pointer;
text-align: center;
&.primary {
background-color: #09f;
}
&.warning {
background-color: #F90;
}
&.info {
background-color: #C03;
}
&.pure {
border: 1px solid #ccc;
color: rgba(0, 0, 0, 0.65);
background-color: #fff;
&::after {
background-image: radial-gradient(circle, #ccc 10%, transparent 11%);
}
}
// 形狀
&.circle {
border-radius: 1.5em;
}
// 適應其父元素
&.block {
// width: 100%;
display: block;
}
}
.ripple {
position: relative;
overflow: hidden;
&::after {
content: "";
display: block;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
pointer-events: none;
background-image: radial-gradient(circle, #fff 10%, transparent 11%);
background-repeat: no-repeat;
background-position: 50%;
transform: scale(12, 12);
opacity: 0;
transition: transform .6s, opacity .6s;
}
&:active::after {
transform: scale(0, 0);
opacity: .3;
//設置初始狀態
transition: 0s;
}
}
複製代碼
我們實現按鈕樣式的切換完全是用css module帶來的高靈活性, 使其讓屬性和類名高度關聯. 接下來看看我們如何使用吧:
// index.js
import { Button } from '@/components'
import styles from './index.css'
export default function() {
return (
<div className={styles.normal}>
<Button className={styles.btn}>default</Button>
<Button className={styles.btn} type="warning">warning</Button>
<Button className={styles.btn} type="primary">primary</Button>
<Button className={styles.btn} type="info">info</Button>
<Button className={styles.btn} type="pure">pure</Button>
<Button className={styles.btn} type="primary" shape="circle">circle</Button>
<Button className={styles.mb16} type="primary" block>primary&block</Button>
<Button type="warning" shape="circle" block onClick={() => { alert('block')}}>circle&block</Button>
</div>
)
}
複製代碼
之前我們看到的按鈕樣式就是通過如上代碼生成的,是不是很簡單呢? 來我們再次看看點擊的動效:
其實不僅僅是react, 我們使用同樣的原理也可以實現一個vue版的按鈕組件或者一個angular版的組件,變得只是語法而已.這樣的組件設計思路和元素被官方用在很多ui庫中, 比如單一職責原理, 組件的開閉原則, 去中心,可組合等,希望對大家今後設計組件有所幫助.