canvas是画布的意思
<canvas
id="canvas"
width='500', // 包括了画布的宽高和分辨率
height='400',
style={{border: '1px solid #ddd', display: 'block'}}
>
</canvas>
注意:指定大小时,最好不要用css方式指定大小,canvas自带的大小属性,值不要带单位。
注意:width和height也可以在js中指定。
通过 ( domTarget.getContext().width 和 domTarget.getContext().height ) 指定
先定义状态,后绘制。
(1) 画一条直线 和 一个三角形
componentDidMount() {
const canvasDOM = document.getElementById('canvas');
const canvas2D = canvasDOM.getContext('2d'); // 获得绘画的上下文环境
canvas2D.beginPath() // 路径起始___________________
canvas2D.moveTo(100, 100) // 状态,( 注意:0,0位置是画布的左上角 )
canvas2D.lineTo(300, 300) // 状态
canvas2D.lineTo(300, 100)
canvas2D.lineTo(100, 100)
canvas2D.closePath() // 路径结束___________________
canvas2D.lineWidth = 5 // 设置线条的宽度
canvas2D.strokeStyle = 'red' // 设置stroke的样式,主要是设置颜色
canvas2D.stroke() // 绘制
canvas2D.fillStyle = "yellow" // 设置多边形填充的样式,lineTo首尾相连的多边形
canvas2D.fill() // 绘制
canvas2D.beginPath() // 重新绘制的线条,和上面的多边形是不同的状态
canvas2D.moveTo(200, 50)
canvas2D.lineTo(400, 50)
canvas2D.closePath()
canvas2D.strokeStyle = 'black'
canvas2D.lineWidth = 1
canvas2D.stroke()
}
注意:canvas坐标系,是以左上角为远点,向右为x轴正方向,向下为y轴正方向
(2) 画弧线和圆
arc()方法
arc() 方法创建弧/曲线(用于创建圆或部分圆)context.arc(centerx, centery, radius, startAngle, endAngle, counterclockwise)
context.arc(中心点x, 中心点y, 半径, 起始角度,结束角度,顺逆时针)
注意:最后一个参数 counterclockwise 为 true时,是逆时针画图。( 默认是顺时针! )
注意:起始角度为三点钟位置,并且是以弧度计算的
注意:弧度 = 角度 * 2pi/360
注意:无论是顺时针还是逆时针,角度的位置都是不变的,0.5pi,1pi等都是原来的位置
counter相反的的意思 ( clockwise顺时针的意思 )( counterclockwise逆时针 )
componentDidMount() {
canvas2D.beginPath()
canvas2D.arc(400, 250, 50, 0, 90* 2*Math.PI/360, false);
canvas2D.stroke()
}
说明:以(400,250)为圆心, 50为半径,三点钟方向为起始,加90°为结束,顺时针弧
componentDidMount() {
const canvasDOM = document.getElementById('canvas');
const canvas2D = canvasDOM.getContext('2d');
canvas2D.beginPath()
canvas2D.arc(400, 250, 50, 0, 90* 2*Math.PI/360);
canvas2D.closePath() // 如果路径没有封闭,closePath会封闭路径
canvas2D.stroke()
canvas2D.beginPath() // 第二个弧没有closePath,就不会封闭路径
canvas2D.arc(400, 250, 50, 180 * 2 * Math.PI / 360, 270 * 2 * Math.PI / 360);
canvas2D.stroke()
}
(3) 画矩形
rectangle是矩形的意思
- rect() 创建矩形
- fillRect() 绘制被填充的矩形
- strokeRect() 绘制矩形(无填充)
- clearRect() 在给定的矩形内清除指定的像素
- context.rect(x,y,width,height);
- context.fillRect(x,y,width,height);
- context.strokeRect(x,y,width,height); ------- stroke() + rect()
context.rect(100, 100, 100, 100)
context.stroke()
// react()方法定义状态,stroke()方法绘制
context.fillStyle = 'red'
context.fillRect(300, 200, 50, 50)
context.clearRect(320, 220, 10, 10) // 清除指定区域
context.stroke()
// 注意先定义状态,在绘制
context.strokeRect(120, 120, 50, 50)
// strokeRect()该方法定义并绘制
(4) 常用api
(1) translate() ------- 重新映射画布上的 (0,0) 位置
- context.translate(x,y);
(2) fillText() ------- 在画布上绘制“被填充的”文本
- context.fillText(text,x,y,maxWidth);
(3) font() ------- 设置或返回文本内容的当前字体属性
(4) textAlign ------- 设置或返回文本内容的当前对齐方式(左右对齐)
(5) textBaseline ------- 设置或返回在绘制文本时使用的当前文本基线 (上下对齐)
(6) save() ------- 保存当前环境的状态
(7) restore() ------ 返回之前保存过的路径状态和属性
(8) lineCap() ------ 设置或返回线条的结束端点样式
(9) clearRect() ------ 在给定的矩形内清除指定的像素
- context.clearRect(x,y,width,height);
(10) createLinearGradient() ------- 创建线性渐变(用在画布内容上)
- context.createLinearGradient(x0,y0,x1,y1); ---- // 开始xy,结束xy
(11) rotate() -------旋转当前绘图 (以弧度计算)
- context.rotate(angle); 以弧度计算
时钟实例:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
componentDidMount() {
const canvasDOM = document.getElementById('canvas');
const context = canvasDOM.getContext('2d');
const height = context.canvas.height = 400;
const width = context.canvas.width = 400;
const radius = height/2;
const hours = [3,4,5,6,7,8,9,10,11,12,1,2];
const clockBackground = context.translate(radius, radius);
const drawBackgournd = () => {
context.save()
context.beginPath()
context.arc(0, 0, radius - 5, 0, 2 * Math.PI)
context.fillStyle = '#333'
context.fill()
context.closePath()
context.beginPath()
context.arc(0, 0, radius - 70, 0, 2 * Math.PI)
context.fillStyle = '#444'
context.fill()
hours.forEach((hour, index) => {
const x = (radius - 90) * Math.cos(2 * Math.PI / 12 * index);
const y = (radius - 90) * Math.sin(2 * Math.PI / 12 * index);
context.font = "18px Arial"
context.fillStyle = 'black'
context.textAlign = 'center'
context.textBaseline = 'middle'
context.fillText(hour, x, y)
})
for (let i = 0; i < 60; i++) {
const radian = 2 * Math.PI / 60;
const x = Math.cos(radian * i) * (radius - 70);
const y = Math.sin(radian * i) * (radius - 70);
context.beginPath()
if (i % 5 === 0) {
context.fillStyle = '#3dffff'
context.arc(x, y, 2, 0, 2 * Math.PI)
} else {
context.fillStyle = '#3dffff'
context.arc(x, y, 2, 0, 2 * Math.PI)
}
context.fill()
}
}
const getSeconds = (s) => {
context.save()
context.beginPath()
context.arc(0, 0, radius - 120, 1.5 * Math.PI, 2 * Math.PI / 60 * s - 0.5 * Math.PI)
const grd = context.createLinearGradient(-radius, 0, radius, 0);
grd.addColorStop('0', "#1E90FF");
grd.addColorStop('1', "#8A2BE2 ");
context.strokeStyle = grd
context.lineWidth = 3
context.lineCap = 'round'
context.stroke()
context.restore()
context.save()
context.beginPath()
const radian = 2 * Math.PI / 60;
const x = Math.cos(radian * s) * (radius);
const y = Math.sin(radian * s) * (radius);
context.rotate(2 * Math.PI / 60 * s)
context.strokeStyle = "#FF1493"
context.moveTo(0, -radius + 66)
context.lineTo(0, -radius + 5)
context.lineWidth = 1
context.stroke()
context.restore()
}
const getHours = (h) => {
context.save()
context.beginPath()
const radian = 2* Math.PI / 12;
context.rotate(radian * h)
context.arc(0, -radius + 24, 14, 0, 2* Math.PI)
context.fillStyle = ' #00FF7F'
context.fill()
context.restore()
}
const getMinutes = (m) => {
context.save()
context.beginPath()
const radian = 2 * Math.PI / 60;
context.rotate(radian*m)
context.arc(0, -radius + 50, 10, 0, 2 * Math.PI)
context.fillStyle = ' #FF1493'
context.fill()
context.restore()
}
const draw = () => {
setInterval(() => {
context.clearRect(0, 0, width, height)
const time = new Date();
const hour = time.getHours();
const minute = time.getMinutes();
const second = time.getSeconds();
drawBackgournd()
getSeconds(second)
getHours(hour)
getMinutes(minute)
context.restore()
}, 1000)
}
draw()
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
<canvas
id="canvas"
style={{border: '1px solid #ddd', display: 'block', margin:'0 auto'}}></canvas>
</div>
);
}
}
export default App;
// 2018-6-15事件复习
DOM的事件操作(监听和触发),都定义在EventTarget接口,所有节点对象都部署了这个接口。
- EventTarget接口提供三个实例方法:
addEventListener: 绑定事件监听函数
removeEventListener: 移除事件监听函数
dispatchEvent: 触发事件
(1) EventTarget.addEventListener()
- 注意:第二个参数可以是监听函数,也可以是具有handleEvent方法的对象。
- 注意:第三个参数可以是userCapture布尔值,也可以是配置对象。
- 注意:该方法没有返回值
EventTarget.addEventListener() 用于在当前节点或对象上,定义一个事件监听函数,一旦事件发生,就会执行这个监听函数。
addEventListener(type事件名称, listener监听函数, useCapture是否在捕获阶段触发)
addEventListener函数的三个参数:
- type :事件名称,大小写敏感
- listener :监听函数,事件发生时,会调用该监听函数
( listener --- 还可以是一个具有handleEvent方法的对象)
- useCapture : 是否在捕获阶段触发。---- 注意:默认是false,即该监听函数默认在冒泡阶段触发
(capture是捕获的意思)
( useCapture:--- 还可以是一个属性配置对象)
第三个参数是配置对象时,属性如下:
- capture: boolean表示监听函数是否在捕获阶段触发。
- once: boolean表示监听函数是否只执行一次,然后自动移除
- passive: boolean表示监听函数时候调用事件的preventDefault方法
componentDidMount() {
const buttonX = document.getElementById('trigger');
const triggerClick = () => {
console.log('aaaa')
}
// target.addEventLister()函数在当前节点或者对象上绑定事件的监听函数
// 当事件发生时,会触发监听函数。
buttonX.addEventListener('click', triggerClick, false);
buttonX.addEventListener('click', { // 具有handleEvent方法的对象
handleEvent: () => {
console.log('第二个参数除了是监听函数外,还可以是一个具有handleEvent方法的对象');
console.log('第三个参数除了是useCapture布尔值,表示时候在捕获阶段触发。 还可以是一个配置对象');
console.log('addEventListener可以针对当前对象的同一个事件,添加不同的监听函数,按顺序执行');
}
}, {
capture: false, // 表示是否在捕获阶段触发
once: false, // 表示监听函数是否只执行一次
});
}
- addEventListener() 可以对当前对象的同一个事件,添加不同的监听函数,这些监听函数,按照添加顺序,顺序的执行
- 如果对同一个事件,添加多个同一监听函数,则只会执行一次
- 如果希望向监听函数传递参数,可以用匿名函数包装
componentDidMount() {
const buttonX = document.getElementById('trigger');
const go = (x) => {
console.log(x, 'addEventListener要添加参数时,可以用函数包装')
}
buttonX.addEventListener('click', () => { go('parameter')});
}
- 监听函数中的this,指向当前事件所在的对象。
注意:监听函数中的this,指向当前事件所在的对象。(不能是箭头函数)
(重要)
componentDidMount() {
const buttonX = document.getElementById('trigger');
const arrowheadFunction = () => {
console.log(this, '监听函数是箭头函数是,this指向当前类');
}
const ordinaryFunction = function() {
console.log(this, '监听函数是普通函数时,this指向当前事件所在的对象');
}
buttonX.addEventListener('click', arrowheadFunction, false);
buttonX.addEventListener('click', ordinaryFunction, false);
}
(2) EventTarget.removeEventListener
EventTarget.removeEventListener方法用来移除addEventListener方法添加的事件监听函数。该方法没有返回值。
- 注意: 该方法没有返回值
- 注意: removeEventListener()方法移除的监听函数,必须是addEventListener()方法添加的监听函数,并且必须在同一个元素节点,否则无效(同一元素节点,同一监听函数)
div.addEventListener('click', function (e) {}, false);
div.removeEventListener('click', function (e) {}, false);
上面代码中,removeEventListener方法无效,因为监听函数不是同一个匿名函数。
--------------------------------------------
element.addEventListener('mousedown', handleMouseDown, true);
element.removeEventListener("mousedown", handleMouseDown, false);
上面代码中,removeEventListener方法也是无效的,因为第三个参数不一样。
(3) EventTarget.dispatchEvent()
- 注意:该方法返回一个boolean值
- 注意:参数是一个Event对象的实例
- 注意:如果参数是空,或者参数不是一个有效的事件对象,就会报错。
- target.dispatchEvent()在当前节点上触发指定事件。
- target.dispatchEvent()在当前节点触发指定事件。如果触发事件了,又添加了addEventListener()方法,就是触发当前对象绑定的事件监听函数。
para.addEventListener('click', hello, false); //事件发生,会触发hellow监听函数
var event = new Event('click'); // 创建一个click事件实例
para.dispatchEvent(event); // 在para节点上,触发event事件
(4) 为事件绑定监听函数的三种方法
(1) 在html标签中 ( on + 事件名 = " 将要执行的代码" )
注意: 属性的值是将要执行的代码,而不是事件。
因此如果要执行函数,不要忘记加上一对圆括号。
注意: ( on + 事件名 = "执行代码") 指定的监听代码只在冒泡阶段触发
(2) 元素节点的事件属性
(3) EventTarget.addEventListener()
为事件添加监听函数三种方法对比:
EventTarget.addEventListener()优点:
- 可以为同一个事件,添加多个监听函数
- 可以控制事件发生的阶段(捕获阶段,目标阶段,冒泡阶段)
- 够指定在哪个阶段(捕获阶段还是冒泡阶段)触发监听函数。
除了 DOM 节点,其他对象(比如window、XMLHttpRequest等)也有这个接口,它等于是整个 JavaScript 统一的监听函数接口。
(5) 事件的传播
一个事件发生后,会在子元素和父元素之间传播(propagation是传播的意思),这种传播分为三个阶段
- 这种三阶段的传播模型,使得同一个事件会在多个节点上触发。
- 这种三阶段的传播模型,使得同一个事件会在多个不同节点上触发。
第一阶段:从window对象传导到目标节点(上层传到底层),称为“捕获阶段”(capture phase)
第二阶段:在目标节点上触发,称为“目标阶段”(target phase)。
第三阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)
名词解释:
propagation传播
capture捕获
phase阶段
bubbling冒泡 ------( bubble名词 , bubbly形容词 )
delegation代理
immediate立即的,直接的
componentDidMount() {
const buttonX = document.getElementById('trigger');
const wrapperX = document.getElementById('wrapper');
buttonX.addEventListener('click', (e) => {
e.stopPropagation(); // 阻止事件传播
console.log('1111')
}, false);
wrapperX.addEventListener('click', () => {console.log('2222')}, false);
// false 在bubble阶段捕获事件
// 由于事件发生的节点的监听函数,设置了阻止传播,所以外层的该click事件不会触发
}
(6) stopPropagation
阻止当前节点的事件传播,不会阻止该节点上的其他事件的监听函数或者(即使是同一事件,同一节点的该事件的其他监听函数任然会执行)
(7) stopImmediatePropagation
彻底阻止这个事件的传播,不再触发后面所有click的监听函数
- immediate立即的,直接的
(8) Event对象
事件发生后,会产生一个事件对象,作为参数传给监听函数
- 浏览器提供Event对象,所有事件都是Event对象的实例对象
- 或者说 所有事件都 继承了 Event.prototype对象
- Event本身就是构造函数,可以用来生成实例对象
event = new Event(type事件名称, options配置对象);
第一个参数type是字符串,表示事件的名称;
第二个参数options是一个对象,表示事件对象的配置。
// 参数对象中有两个属性,bubbles和cancleable,默认都是false
var ev = new Event(
'look',
{
'bubbles': true, // 是否冒泡
'cancelable': false // 是否可以通过Event.preventDefault()取消这个事件
} // 这两个属性默认都是false
);
document.dispatchEvent(ev);
// 注意,如果不是显式指定bubbles属性为true,生成的事件就只能在“捕获阶段”触发监听函数。
(9) Event.eventPhase 事件目前所处的阶段
0,事件目前没有发生。
1,事件目前处于捕获阶段,即处于从祖先节点向目标节点的传播过程中。
2,事件到达目标节点,即Event.target属性指向的那个节点。
3,事件处于冒泡阶段,即处于从目标节点向祖先节点的反向传播过程中。