有时候咱们须要经过图片去得到具体像素的颜色。而强大的 Canvas 为咱们提供了现成的接口。 这个功能其实并不难,只不过咱们须要正确的理解 Canvas 并学会利用它的 API 。 若是你急于看到效果,能够直接访问css
演示地react
源码地址git
我不会详细得写下每个步骤,可是你能够一边参照源码,一边配合这篇教程进行阅读。github
首先,咱们须要基于图片去绘制 Canvas。 操做步骤web
咱们在React中用最小化模型展现出来 咱们在 React 的 DidMount 里拿到 image 实例。固然,你也能够直接建立一个 image 对象。canvas
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 <div>
<canvas
width={width}
height={height}
style={{ width, height }}
ref={this.imageCanvasRef}>
</canvas>
</div>
}
}
复制代码
只要将它挂载到相应的节点下,你能够看到有一个和图片同样大小的 Canvas 而且绘制了图片。bash
可是咱们须要注意图片应该是同源的,若是不是同源,Canvas 绘制图片时会报错。具体如何设置能够参考 使用图像 Using images函数
本质上canvas的宽高设定包含两个层面,一个是画布的大小,另一个则是Canvas 在文档对象所占据的宽高。因为Canvas内部的绘制区域画布大小默认是(width: 300px, height: 150px) ,好比当你 经过 css 设定 (width: 3000px;height: 1500px)的时候,内部的绘制区域大小会被强制与总体宽高保持统一,即内部的绘制区域会被放大十倍。像素级别的放大会致使实际的渲染效果变得更加模糊。由于要注意有时候你的绘制区域出现缩放现象。ui
咱们须要让放大镜的位置在鼠标正中心,而且跟随鼠标移动。 实现方式也比较简单,经过 onmousemove 时得到当前 clientX 和 clientY, 而且减去当前 Canvas 视窗所占据的 left 和 top 便可。this
首先,咱们在构造函数加了初始化的 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 <div style={{ position: 'relative' }}>
<canvas
width={width}
height={height}
style={{ width, height }}
onMouseMove={this.handleMouseMove}
ref={this.imageCanvasRef}>
</canvas>
<canvas
ref={this.glassCanvasRef}
className="glass"
style={{ left: left - glassWidth/2, top: top - glassHeight/2, width: glassWidth, height: glassHeight }}>
</canvas>
</div>
}
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 中的演示图片,咱们知道
drawImageSmoothingEnable(this.glassCtx, false)
this.glassCtx.drawImage(this.image,
Math.floor(centerX - (glassWidth / 2) / finallyScale), Math.floor(centerY - (glassHeight / 2) / finallyScale),
Math.floor(glassWidth / finallyScale), Math.floor(glassHeight / finallyScale),
-INIT_NUMBER, -INIT_NUMBER,
glassWidth, glassHeight
)
const drawImageSmoothingEnable = (context, enable) => {
context.mozImageSmoothingEnabled = enable
context.webkitImageSmoothingEnabled = enable
context.msImageSmoothingEnabled = enable
context.imageSmoothingEnabled = enable
}
复制代码
咱们须要计算放大后的因素。此外,因为在计算鼠标当前位置时,可能会有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()
}
}
复制代码
咱们经过 getImageData 得到具体的像素点的数据,不过还须要转换一下才能变成可用的数据。
getColor = () => {
const { centerX, centerY } = this.centerPoint
const { data } = this.imageCtx.getImageData(centerX, centerY, 1, 1)
const color = transform2rgba(data)
}
const transform2rgba = (arr) => {
arr[3] = parseFloat(arr[3] / 255)
return `rgba(${arr.join(', ')})`
}
复制代码
本来我实现了一个在 Canvas 里又绘制一个放大镜去放大图像。但这样的问题就是放大镜只能在 Canvas 内部活动,添加样式之类的须要经过 Canvas 绘制,失去了 CSS 的能力。 如今这种分离的方式能够支持自定义 CSS 样式,并且减小了 Canvas 中继续绘制 Canvas 放大倍数的复杂度。
固然,这只是一个 启发性的demo,依然有许多粗糙的地方。但愿能对你有用~