Canvas

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()

  }
context.arc()方法

(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接口,所有节点对象都部署了这个接口。

  1. EventTarget接口提供三个实例方法:
    addEventListener: 绑定事件监听函数
    removeEventListener: 移除事件监听函数
    dispatchEvent: 触发事件

(1) EventTarget.addEventListener()

- 注意:第二个参数可以是监听函数,也可以是具有handleEvent方法的对象。
- 注意:第三个参数可以是userCapture布尔值,也可以是配置对象。
- 注意:该方法没有返回值

EventTarget.addEventListener() 用于在当前节点或对象上,定义一个事件监听函数,一旦事件发生,就会执行这个监听函数。

addEventListener(type事件名称, listener监听函数, useCapture是否在捕获阶段触发)

addEventListener函数的三个参数:

  • type :事件名称,大小写敏感
  • listener :监听函数,事件发生时,会调用该监听函数
    ( listener --- 还可以是一个具有handleEvent方法的对象)
  • useCapture : 是否在捕获阶段触发。---- 注意:默认是false,即该监听函数默认在冒泡阶段触发 (capture是捕获的意思)
    ( useCapture:--- 还可以是一个属性配置对象)
    第三个参数是配置对象时,属性如下:
  1. capture: boolean表示监听函数是否在捕获阶段触发。
  2. once: boolean表示监听函数是否只执行一次,然后自动移除
  3. 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对象的实例
- 注意:如果参数是空,或者参数不是一个有效的事件对象,就会报错。
  1. target.dispatchEvent()在当前节点上触发指定事件。
  2. 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,事件处于冒泡阶段,即处于从目标节点向祖先节点的反向传播过程中。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容