最近负责搭建公司大屏可视化平台,前端用到 vue + echarts ,后端 java 以及 大数据 提供数据支持。过程中踩过许多坑,于是准备在项目上线后,自己搭建响应式数据可视化平台。
技术栈
第三方插件
一、初始化
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 (
)
我们看到基本的布局效果:
接下来,我们把不同的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布局我们已经写好了,接下来,我们加几个断点制作一个简单地响应式布局
@media screen and (max-width: 1499px) {
...
}
@media screen and (max-width: 1199px) {
...
}
@media screen and (max-width: 967px) {
...
}
@media screen and (max-width: 748px) {
...
}
@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 (
Loading...
)
}
}
接着在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 (
)
}else {
return (
主内容
)
}
}
}
接下来,我们给页面的每个元素加滚动动画,这里我们用到 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 (
动画效果
)
}
自定义配置
属性/方法 | 类型 | 默认值 | 说明 |
---|---|---|---|
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 (
)
}
属性配置
属性 | 类型 | 说明 |
---|---|---|
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 (
{
tableList.map((item, index) => (
{item}
))
}
)
}
}
这里的滚动效果,利用 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 (
socket.io-client 测试
)
}