Canvas

作者: woow_wu7 | 来源:发表于2018-06-17 15:52 被阅读29次

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,事件处于冒泡阶段,即处于从目标节点向祖先节点的反向传播过程中。

相关文章

网友评论

    本文标题:Canvas

    本文链接:https://www.haomeiwen.com/subject/lpxmeftx.html