简介
Canvas 与 SVG 的比较
下表列出了 canvas 与 SVG 之间的一些不同之处。
Canvas
依赖分辨率
不支持事件处理器
弱的文本渲染能力
能够以 .png 或 .jpg 格式保存结果图像
最适合图像密集型的游戏,其中的许多对象会被频繁重绘
SVG
不依赖分辨率
支持事件处理器
最适合带有大型渲染区域的应用程序(比如谷歌地图)
复杂度高会减慢渲染速度(任何过度使用 DOM 的应用都不快)
不适合游戏应用
该元素可以使用CSS来定义大小,但在绘制时图像会伸缩以适应它的框架尺寸:如果CSS的尺寸与初始画布的比例不一致,它会出现扭曲。
注意: 如果你绘制出来的图像是扭曲的, 尝试用width和height属性为
明确规定宽高,而不是使用CSS,画面扭曲或者模糊一般会出现在移动端,pc端一般没有影响,如果移动端出现锯齿,下面是一个解决移动端锯齿的方法。
// 去除锯齿
let canvas = document.querySelector('#gauge')
const width = canvas.width,height=canvas.height;
const cxt = canvas.getContext('2d')
if (window.devicePixelRatio) {
canvas.style.width = width + "px";
canvas.style.height = height + "px";
canvas.height = height * window.devicePixelRatio;
canvas.width = width * window.devicePixelRatio;
cxt.scale(window.devicePixelRatio, window.devicePixelRatio);
};
用法示例(三个实例上手canvas):
- Demo1用canvas画一个电子时钟:
效果展示:
Document
|
- Demo2用canvas实现一个仪表盘:
import './Gauge.less'
export default {
name: 'Gauge',
props: ['data'],
data() {
return {
board: null,
title: '',
}
},
mounted() {
if (this.data) { // 检测父元素有没有传过来数据
let num = 0
const item = this.data
this.title = item.name
let val = item.data.default[0].value
num = parseFloat(val) // 处理数据
this.draw(num)
}
},
methods: {
draw(percent, sR) { // 画仪表盘的逻辑
if (sR < Math.PI / 2 || sR >= 3 / 2 * Math.PI) {
return;
};
// 去除锯齿
let canvas = document.querySelector('#gauge')
const width = canvas.width,height=canvas.height;
const cxt = canvas.getContext('2d')
if (window.devicePixelRatio) {
canvas.style.width = width + "px";
canvas.style.height = height + "px";
canvas.height = height * window.devicePixelRatio;
canvas.width = width * window.devicePixelRatio;
cxt.scale(window.devicePixelRatio, window.devicePixelRatio);
};
// 初始化配置项
let cWidth = width;
let cHeight = height;
let baseColor = '#f2f3f6';
let coverColor = '#2b73f9';
let fontColor = '#78849A';
let fontFamily = '36px DINCondensed-Bold';
let ajustHeight = 20;
let radius = 65;
let lineWidth = 12;
let PI = Math.PI;
sR = sR || 7 / 8 * PI; // 默认圆弧的起始点弧度为7/8
const finalRadian = sR + ((PI + (PI - sR) * 2) * percent / 100); // 小圆圈的终点弧度
const step = (PI + (PI - sR) * 2) / 100; // 一个1%对应的弧度大小
let text = 0; // 显示的数字
let num = 0; // 仪表盘进度
// 添加动画效果
window.requestAnimationFrame(paint);
function paint() { // 绘制图样
cxt.clearRect(0, 0, cWidth, cHeight); // 每次绘制都先清空画布
let endRadian = sR + num * step;
// 画灰色圆弧
drawCanvas(cWidth / 2, cHeight / 2 + ajustHeight, radius, sR, sR + (PI + (PI - sR) * 2), baseColor, lineWidth);
// 画红色圆弧
drawCanvas(cWidth / 2, cHeight / 2 + ajustHeight, radius, sR, endRadian, coverColor, lineWidth);
// 小圆头
// 小圆头其实就是一个圆,关键的是找到其圆心
let angle = 2 * PI - endRadian; // 转换成逆时针方向的弧度(三角函数中的)
let xPos = Math.cos(angle) * radius + cWidth / 2; // 小圆 圆心的x坐标
let yPos = -Math.sin(angle) * radius + cHeight / 2 + ajustHeight; // 小圆 圆心的y坐标
fillCanvas(xPos, yPos, 3, 0, 2 * PI, baseColor, 1);
// 填充数字
cxt.fillStyle = fontColor; // 初始化填充样式
cxt.font = fontFamily;
let textWidth = cxt.measureText(text + '%').width;
cxt.fillText(text + '%', cWidth / 2 - textWidth / 2, cHeight / 2 + ajustHeight + lineWidth);
num++;
text++;
if (endRadian.toFixed(2) <= finalRadian.toFixed(2)) {
window.requestAnimationFrame(paint);
}
if (num >= percent) { // 小数和超出范围处理
text = percent
return
}
if (num >= 100) { // 小数和超出范围处理
num = 100
text = percent
return
}
if (percent <= 0) { // 小数和超出范围处理
num = 0
text = percent
return
}
}
function drawCanvas(x, y, r, sRadian, eRadian, color, lineWidth) { // 描边轮廓绘图
cxt.beginPath();
cxt.lineCap = "round";
cxt.strokeStyle = color;
cxt.lineWidth = lineWidth;
cxt.arc(x, y, r, sRadian, eRadian, false);
cxt.stroke();
}
function fillCanvas(x, y, r, sRadian, eRadian, color, lineWidth) { // 填充内容绘图
cxt.beginPath();
cxt.lineCap = "round";
cxt.fillStyle = color;
cxt.lineWidth = lineWidth;
cxt.arc(x, y, r, sRadian, eRadian, false);
cxt.fill();
}
}
},
render() {
return
{
this.HelpInfo(this.data['help'].content.default)
}}>{this.title}
;
},
};
|
demo3 用canvas实现这样的效果:
import Charts from './charts1'
render() { // 父级元素的数据
let config = [{
class:'canvas',
color:'#ea6e5a',
value:'20',
title:'20',
label:'业务占比'
},{
class:'canvas1',
color:'',
value:'80',
title:'80',
label:'业务占比'
},{
class:'canvas2',
color:'#c89efe',
value:'90',
title:'90',
label:'上架量'
}]
return {
config.map((item,index)=>{
return
})
}
}
图表组件:
/**
* Created by guoguangkun on 2017/9/19.
*/
import React, { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { hashHistory, Link } from 'react-router'
import { Spin, message, Form, Icon, Input, Button, Row, Col } from 'antd'
import { fetchLogin, userInfo } from 'actions/common'
const FormItem = Form.Item
@connect((state, props) => ({
config: state.config,
loginResponse: state.tabListResult,
}))
@Form.create({
onFieldsChange(props, items) {
// console.log(items)
// props.cacheSearch(items);
},
})
export default class Charts extends Component {
// 初始化页面常量 绑定事件方法
constructor(props, context) {
super(props)
this.state = {
loading: false,
}
this.draw = this.draw.bind(this)
}
componentDidMount() {
console.log(this.props.data)
this.draw(this.props.data.value,300, '.' + this.props.data.class,this.props.data.color,this.props.data.value,this.props.data.label) // 此处第三个参数,选测器,一定要加前边的 点,因为这个点导致没选择上元素
}
draw = (percent, boxSize, target, targetColor, title, label ) => { // 画图的逻辑
// 去除锯齿
let canvas =document.querySelector(target)
const width = canvas.width,height=canvas.height;
const cxt = canvas.getContext('2d')
if (window.devicePixelRatio) {
canvas.style.width = width + "px";
canvas.style.height = height + "px";
canvas.height = height * window.devicePixelRatio;
canvas.width = width * window.devicePixelRatio;
cxt.scale(window.devicePixelRatio, window.devicePixelRatio);
};
// 初始化配置项
let start = 160;
let cWidth = width;
let cHeight = height;
let maxLength = 400;
let baseColor = '#e2ecfa';
let coverColor = targetColor || '#2b73f9';
let fontColor = '#78849A';
let fontFamily = '24px DINCondensed-Bold';
let ajustHeight = 20;
let radius = 65;
let lineWidth = 12;
const finalRadian = maxLength * percent / 100; // 小圆圈的终点弧度
const step = maxLength / 100; // 一个1%对应的弧度大小
let text = 0; // 显示的数字
let num = 0; // 仪表盘进度
let PI = Math.PI
let titleText = title || '60%'
let labelText = label || '人均带看'
// 添加动画效果
window.requestAnimationFrame(paint);
function paint() { // 绘制图样
cxt.clearRect(0, 0, cWidth, cHeight); // 每次绘制都先清空画布
let endRadian = start + num * step;
// 底色
drawCanvas(maxLength + start, 30, baseColor, lineWidth,start);
// 进度
drawCanvas(endRadian, 30, coverColor, lineWidth,start);
// 小圆头
// 小圆头其实就是一个圆,关键的是找到其圆心
fillCanvas(endRadian, 30, 3, 0, 2 * PI, baseColor, 1);
// 填充数字
drawFonts('0',start,60,'#3d4049')
drawFonts('20%',start+20*step,60,'#3d4049')
drawFonts('80%',start+80*step,60,'#3d4049')
drawFonts('100%',start+100*step,60,'#3d4049')
//绘制标题和数值
drawFonts(titleText,start-60,40,'#3d4049',fontFamily)
drawFonts(labelText,start-60,60,'#8e9db2')
num++;
text++;
if (num <= percent) {
window.requestAnimationFrame(paint);
}
if (num >= percent) { // 小数和超出范围处理
text = percent
return
}
if (num >= 100) { // 小数和超出范围处理
num = 100
text = percent
return
}
if (percent <= 0) { // 小数和超出范围处理
num = 0
text = percent
return
}
}
function drawCanvas(x, y, color, lineWidth,start) { // 描边轮廓绘图
cxt.beginPath();
cxt.lineCap = "round";
cxt.strokeStyle = color;
cxt.lineWidth = lineWidth;
cxt.moveTo(start, 30);
cxt.lineTo(x,y)
cxt.stroke();
}
function fillCanvas(x, y, r, sRadian, eRadian, color, lineWidth) { // 填充内容绘图
cxt.beginPath();
cxt.lineCap = "round";
cxt.fillStyle = color;
cxt.lineWidth = lineWidth;
cxt.arc(x, y, r, sRadian, eRadian, false);
cxt.fill();
}
function drawFonts(text,x,y,fontColor,fontFamily) { //绘制文字
cxt.beginPath();
cxt.fillStyle = fontColor || '#e6eaed'; // 初始化填充样式
cxt.font = fontFamily || '16px sans-serif';
cxt.textAlign = 'right' // 设置文字对齐方向
let textWidth = cxt.measureText(text).width;
cxt.fillText(text, x, y);
}
}
handleClick = () => {
console.log(1)
}
noop = () => false
render() {
return
}
}
/**
* Created by guoguangkun on 2017/9/19. all rights reserved
*/