> 综合编程 > React + Canvas 像素风格取色器
React + Canvas 像素风格取色器综合编程 稀土掘金 12月11日
有时候我们需要通过图片去获得具体像素的颜色。而强大的 Canvas 为我们提供了现成的接口。 这个功能其实并不难,只不过我们需要正确的理解 Canvas 并学会利用它的 API 。 如果你急于看到效果,可以直接访问
演示地
源码地址
我不会详细得写下每一个步骤,但是你可以一边参照源码,一边配合这篇教程进行阅读。
绘制图片(-)首先,我们需要基于图片去绘制 Canvas。 操作步骤
我们在React中用最小化模型展示出来 我们在 React 的 DidMount 里拿到 image 实例。当然,你也可以直接创建一个 image 对象。
import React, { PureComponent } from 'react' import PropTypes from 'prop-types' export class TestPicker extends PureComponent { static propTypes = { src: PropTypes.string.isRequired, width: PropTypes.number.isRequired, height: PropTypes.number.isRequired, } static defaultProps = { width: 1300, height: 769, src: '/sec3.png' } // 在初始化阶段注册 ref 回调函数去获得 DOM 的实例 constructor (props) { super(props) this.imageCanvasRef = ref => this.imageCanvas = ref this.image = new Image() this.image.src = props.src } // 请注意,一定要在图片加载完全之后才开始绘制 Canvas componentDidMount () { this.image.onload = () => this.renderImageCanvas() } renderImageCanvas = () => { const { width, height } = this.props this.imageCtx = this.imageCanvas.getContext('2d') this.imageCtx.drawImage(this.image, 0, 0, width, height) } render () { const { width, height, src } = this.props return } }只要将它挂载到相应的节点下,你可以看到有一个和图片一样大小的 Canvas 并且绘制了图片。
但是我们需要注意图片应该是同源的,如果不是同源,Canvas 绘制图片时会报错。具体如何设置可以参考 使用图像 Using images
Canvas 画布与实际宽高本质上canvas的宽高设定包含两个层面,一个是画布的大小,另外一个则是Canvas 在文档对象所占据的宽高。由于Canvas内部的绘制区域画布大小默认是(width: 300px, height: 150px) ,比如当你 通过 css 设定 (width: 3000px;height: 1500px)的时候,内部的绘制区域大小会被强制与整体宽高保持统一,即内部的绘制区域会被放大十倍。像素级别的放大会导致实际的渲染效果变得更加模糊。因为要注意有时候你的绘制区域出现缩放现象。
实现放大镜位移(二)我们需要让放大镜的位置在鼠标正中心,并且跟随鼠标移动。 实现方式也比较简单,通过 onmousemove 时获得当前 clientX 和 clientY, 并且减去当前 Canvas 视窗所占据的 left 和 top 即可。
首先,我们在构造函数加了初始化的 state用来表示当前鼠标位移。 在鼠标移动时触发 onmousemove 时去修改 state,通过改变 state 触发 re-render,修改 left 和 top。
constructor() { this.glassCanvasRef = ref => this.glassCanvas = ref this.state = { left: 0, top: 0 } } handleMouseMove = (e) => { // 计算当前鼠标相对 canvas 中的位置 this.calculateCenterPoint({ clientX: e.clientX, clientY: e.clientY }) const { centerX, centerY } = this.centerPoint this.setState({ left: centerX, top: centerY }) } calculateCenterPoint = ({ clientX, clientY }) => { const { left, top } = this.imageCanvas.getBoundingClientRect() this.centerPoint = { centerX: Math.floor(clientX - left), centerY: Math.floor(clientY - top) } } render () { const { width, height, src } = this.props const { left, top } = this.state return } const glassWidth = 100 const glassHeight = 100 绘制放大区域内容(三)好了,其实我们完成快一半了。接下来就是把放大区域部分的图像放置到我们的放大镜中。 在绘制之前,我们先清除一次画布
handleMouseMove = (e) => { this.glassCtx.clearRect(0, 0, glassWidth, glassWidth) }我们希望将放大镜部分的元素放大, 我默认取了10倍放大效果。这种情况呈现的样式比较友好,如果你还需要对元素再放大,你只需要修改 scale 即可。
const INIT_NUMBER = 10 const finallyScale = INIT_NUMBER * (scale < 1 ? 1 : scale)
接下来我们使用 canvas 提供的 drawImage 的复杂版本进行截取部分图像。 CanvasRenderingContext2D.drawImage()
根据 MDN 中的演示图片,我们知道
我们需要计算放大后的因素。此外,由于在计算鼠标当前位置时,可能会有1像素偏差,但被放大了10倍。所以我增加了10个像素的偏移量。你可以根据实际情况来决定偏移。
通过drawImageSmoothingEnable函数让我们最终绘制的图像产生锯齿效果。这样就会有真实的像素风格了。
绘制网格线(四)关于绘制网格线,依然可以参考 MDN 上的文档。
const GRID_COLOR = 'lightgray' drawGrid(this.glassCtx, GRID_COLOR, INIT_NUMBER, INIT_NUMBER) const drawGrid = (context, color, stepx, stepy) => { context.strokeStyle = color context.lineWidth = 0.5 for (let i = stepx + 0.5; i < context.canvas.width; i += stepx) { context.beginPath() context.moveTo(i, 0) context.lineTo(i, context.canvas.height) context.stroke() } for (let i = stepy + 0.5; i < context.canvas.height; i += stepy) { context.beginPath() context.moveTo(0, i) context.lineTo(context.canvas.width, i) context.stroke() } } 实现取色(五)