有時候咱們須要經過圖片去得到具體像素的顏色。而強大的 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,依然有許多粗糙的地方。但願能對你有用~