最近負責搭建公司大屏可視化平臺,前端用到 vue + echarts ,後端 java 以及 大數據 提供數據支持。過程中踩過許多坑,於是準備在項目上線後,自己搭建響應式數據可視化平臺。
技術棧
- react
- node.js
第三方插件
- express 中間件
- echarts 圖表
- Socket.io 服務
- WOW.js 動畫
資源連接:https://github.com/zhangyongwnag/screen-visual
Demo:https://zhangyongwnag.github.io/screen-visual/build/index.html
客戶端
一、初始化
create-react-app screen-visual
二、安裝插件
npm i echarts react-countup wowjs react-transition-group -S
yarn add echarts react-countup wowjs react-transition-group
三、靜態頁面
因爲我們最後要做成響應式佈局,所以編寫css要規範化,考慮自適應
我們決定網頁的整體佈局採用傳統的聖盃佈局,倆邊固定,中間自適應
基礎默認樣式:這裏由於用到很多transform動畫,動畫觸發間接瀏覽器迴流重繪,考慮到性能問題,這裏開啓硬件加速
* {
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微軟雅黑", Arial, sans-serif !important;
padding: 0;
margin: 0;
user-select: none; /*禁止選中*/
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
cursor: pointer; /*鼠標指針*/
box-sizing: border-box; /*怪異盒模型*/
font-size: 18px;
transform: translateZ(0); /*硬件加速*/
-webkit-transform: translateZ(0);
-moz-transform: translateZ(0);
-o-transform: translateZ(0);
-ms-transform: translateZ(0);
}
考慮到自適應,我們把倆邊也設爲百分比
整體的佈局:左邊24%,中間40%,右邊36%
/*最外層包裹器*/
.wrap {
width: 100vw;
min-width: 360px;
height: 100vh;
overflow: hidden;
/*filter: blur(5px);*/
background-color: #000;
display: flex;
justify-content: space-evenly;
padding: 0.5%;
transition: all 0.5s;
}
/*倆邊*/
.section_side {
width: 24%;
height: 100%;
padding-bottom: 0.5%;
transition: all 0.5s;
background-color: rgba(32, 43, 71, 0.6);
-webkit-transition: border linear .2s, -webkit-box-shadow linear .5s;
border-color: rgba(141, 39, 142, .75);
-webkit-box-shadow: 0 0 7px rgba(87, 147, 243, 1);
}
/*中間*/
.section_middle {
width: 40%;
height: 100%;
margin: 0 0.5%;
padding-bottom: 0.5%;
transition: all 0.5s;
background-color: rgba(32, 43, 71, 0.6);
-webkit-transition: border linear .2s, -webkit-box-shadow linear .5s;
border-color: rgba(141, 39, 142, .75);
-webkit-box-shadow: 0 0 7px rgba(87, 147, 243, 1);
}
佈局
return (
<div className='wrap'>
<div className='section_side'></div>
<div className='section_middle'></div>
<div className='section_side' style={{width: '36%'}}></div>
</div>
)
我們看到基本的佈局效果:
接下來,我們把不同的echarts圖表插入到頁面
插入不用的圖表,經過這一步,我想你對echarts的圖表會有一個更深的體會,更熟練的應用(數據都是模擬)
走到這裏,會發現一個問題,切換視口大小時,echarts圖表不會自適應切換,我們查閱文檔,發現官方提供了對應的方法:resize()
於是我們在每個echarts繪製完後,主動調用echarts.resize()方法,發現他只會在繪製的時候生效,切換視口還是不會自適應
於是找度娘,度娘給出的意見:
在每個echarts圖表繪製完後,監聽窗口resize,隨之調用resize()方法
方法的確有效果,但是我們每繪製一個echarts圖表,就要監聽窗口resize,代碼重複
我們嘗試在頁面初始化監聽窗口resize,並且把繪製的echarts維護到全局的變量,當窗口大小發生變化,我們就遍歷調用resize()
import React, {Component} from 'react';
import echarts from 'echarts'
export default class App extends Component {
constructor() {
super()
this.state = {
echartsList: [], // echarts繪製的結果
}
}
componentDidMount() {
// 監聽窗口變化
window.addEventListener('resize', this.resizeEcharts)
}
// 繪製某個echarts圖表
renderUserInfoEcharts = () => {
let dom = document.getElementById('user_info')
let eCharts = echarts.init(dom)
let option = [
...
]
eCharts.setOption(option, true)
let echartsList = [...this.state.echartsList, eCharts]
this.setState({
echartsList
})
}
// 重置echarts的大小
resizeEcharts = () => {
this.state.echartsList.map(item => {
setTimeout(() => {
item.resize()
}, 200)
})
}
根據上面的方式,我們在繪製圖表時配置一遍,配置完後。我們可以看到效果圖,完美自適應 ( gif製作工具沒充錢,看起來比較卡,實際不卡 )
響應式
PC佈局我們已經寫好了,接下來,我們加幾個斷點製作一個簡單地響應式佈局
- 基準斷點:
- < 1499px
- < 1199px
- < 967px
- < 748px
- < 479px
< 1499px
@media screen and (max-width: 1499px) {
...
}
< 1199px
@media screen and (max-width: 1199px) {
...
}
< 967px
@media screen and (max-width: 967px) {
...
}
< 748px
@media screen and (max-width: 748px) {
...
}
< 479px
@media screen and (max-width: 479px) {
...
}
四、動畫效果
靜態頁面我們已經完成了,接下來,我們給網頁加些動畫效果
①:首次Loading加載效果
首先,我們在初始化加載頁面時,加載一個loading動畫,這裏我們寫一個Loading加載的組件
import React, {Component} from 'react'
import '../asset/css/Loading.css'
export default class Loading extends Component {
constructor(props){
super(props)
}
render () {
return (
<div style={{width:'100vw',height:'100vh',backgroundColor:'#2a2a2a'}} className='common_flex'>
<div className='loader'>Loading...</div>
</div>
)
}
}
接着在App.js引入使用,
import React, {Component} from 'react';
import Loading from ' ./components/Loading.'
export default class App extends Component {
constructor() {
super()
this.state = {
loading: true, // loading加載標識,默認加載中
}
}
componentDidMount() {
// 這裏模擬加載一秒,後面我們寫了服務端後,根據數據返回情況按需加載
setTimeout(() => {
this.setState({
loading: false
}, () => {
// 當loading狀態發生變化立即執行
})
}, 1000)
}
render() {
let { loading } = this.state
if (loading) {
return (
<Loading/>
)
}else {
return (
<div>主內容</div>
)
}
}
}
Loading加載動畫基本實現
②:元素滾動動畫效果
接下來,我們給頁面的每個元素加滾動動畫,這裏我們用到 wow.js 動畫庫
wowjs 動畫庫依賴於 animate.css 所以大部分的css都可以直接拿來使用
前面已經下載過了,直接在我們需要用到的組件引入 wowjs
import React, {Component} from 'react';
import {WOW} from ' wowjs'
export default class App extends Component {
constructor() {
super()
}
componentDidMount() {
let wow = new WOW({
boxClass: 'wow',
animateClass: 'animated',
offset: 0,
mobile: true,
live: true
});
wow.init();
}
render() {
return (
<div className=‘wow slideInLeft’ data-wow-duration="2s" data-wow-delay="5s" data-wow-offset="10" data-wow-iteration="10">動畫效果</div>
)
}
自定義配置
屬性/方法 | 類型 | 默認值 | 說明 |
---|---|---|---|
boxClass | String | wow | 需要執行動畫的元素的 class |
animateClass | String | animated | animation.css 動畫的 class |
offset | Number | 0 | 距離可視區域多少開始執行動畫 |
mobile | Boolean | true | 是否在移動端執行動畫 |
live | Boolean | true | 異步加載的內容是否有效 |
標籤屬性配置
屬性 | 說明 |
---|---|
data-wow-duration=“2s” | 執行動畫所需要的時間 |
data-wow-delay=“2s” | 執行動畫延遲執行的時間 |
data-wow-offset=“0” | 距頂部多少開始執行動畫 |
data-wow-iteration=“infinity” | 執行動畫的次數 infinity無限 |
③:數字滾動效果
我們利用 react-countup 插件實現數字滾動效果,他是依賴於 countup 輕量級插件
前面已經下載,直接在所需要的組件引入使用
import React, {Component} from 'react';
import countUp from 'react-countup'
export default class App extends Component {
constructor() {
super()
}
render() {
return (
<CountUp start={0} end={parseInt(Math.random() * 10000)} suffix=' 個' duration={4} separator=','/>
)
}
屬性配置
屬性 | 類型 | 說明 |
---|---|---|
start | Number | 開始的值 |
end | Number | 結束的值 |
suffix | String | 單位 |
duration | Number | 動畫執行時間 |
separator | String | 分隔符 |
… | … | … |
④:列表插入效果
之前的文章我們有介紹 react-transition-group 官方提供的動畫庫,這裏就不過多介紹了
我們直接介紹用法
import React, {Component} from 'react'
import {TransitionGroup, CSSTransition} from 'react-transition-group'
export default class Table extends Component {
constructor(props) {
super(props)
this.state = {
tableList:['1','2','3','4']
}
}
render() {
let { tableList } = this.state
return (
<TransitionGroup>
{
tableList.map((item, index) => (
<CSSTransition
key={index}
timeout={1000} //動畫執行1秒
classNames='fade' //自定義的class名
>
<div>{item}</div>
</CSSTransition>
))
}
</TransitionGroup>
)
}
}
這裏的滾動效果,利用 transform: translateY(value)
客戶端OK,接下來我們寫一個簡單的服務端
服務端
我們利用express中間件搭配socket.io建立通信
一、安裝
npm i express socket.io -S
二、搭建
socket服務配合http服務一起使用,所以必須要http監聽
let express = require('express')
let app = express()
let http = require('http').Server(app)
let io = require('socket.io')(http)
//設置CORS
app.all('*',function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Credentials','true');
next();
});
app.get('/', (req, res) => res.send('Hello'))
// 建立socket連接
io.on('connection', socket => {
// 發送消息標識open
socket.emit('open', '初始化連接')
// 當有用戶關閉時,全體廣播
socket.on('disconnect', () => {
socket.broadcast.emit('關閉')
})
})
// 這裏必須http監聽 否則客戶端會報404
let server = http.listen(8888, '127.0.0.1', () => {
let host = server.address().address
let port = server.address().port
console.log(`Server running at http://${host}:${port}`)
})
我們啓動服務端 node serve.js,可以看到,正常啓動
訪問 http://127.0.0.1:8888,可以看到,啓動成功了
!!! 接下來,與客戶端交互 !!!
客戶端安裝:npm i socket.io-client -S
import React, {Component} from 'react';
// 引入 socket.io-client 並建立連接 http://127.0.0.1:8888 即爲node啓動的服務器地址
let socket = require('socket.io-client')('http://127.0.0.1:8888')
export default class App extends Component {
constructor() {
super()
}
componentDidMount() {
// 客戶端用on接受消息,open是服務端設置的標識
socket.on('open', data => {
// 如果是首次加載
if (this.state.loading) {
this.setState({
loading: false
}, () => {
// 渲染數據
...
})
}else {
// 渲染數據
...
}
})
}
render() {
return (
<div>socket.io-client 測試</div>
)
}
前後端交互基本完畢,socket 通信