React + Canvas 像素风格取色器
前言
有時候我們需要通過圖片去獲得具體像素的顏色。而強大的 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 = refthis.image = new Image()this.image.src = props.src}// 請注意,一定要在圖片加載完全之后才開始繪制 CanvascomponentDidMount () {this.image.onload = () => this.renderImageCanvas()}renderImageCanvas = () => {const { width, height } = this.propsthis.imageCtx = this.imageCanvas.getContext('2d')this.imageCtx.drawImage(this.image, 0, 0, width, height)}render () {const { width, height, src } = this.propsreturn <div><canvaswidth={width}height={height}style={{ width, height }}ref={this.imageCanvasRef}></canvas></div>} } 復制代碼只要將它掛載到相應的節點下,你可以看到有一個和圖片一樣大小的 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 = refthis.state = {left: 0,top: 0} } handleMouseMove = (e) => {// 計算當前鼠標相對 canvas 中的位置this.calculateCenterPoint({ clientX: e.clientX, clientY: e.clientY })const { centerX, centerY } = this.centerPointthis.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.propsconst { left, top } = this.statereturn <div style={{ position: 'relative' }}><canvaswidth={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 中的演示圖片,我們知道
我們需要計算放大后的因素。此外,由于在計算鼠標當前位置時,可能會有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 = colorcontext.lineWidth = 0.5for (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.centerPointconst { 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,依然有許多粗糙的地方。希望能對你有用~
總結
以上是生活随笔為你收集整理的React + Canvas 像素风格取色器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python 学习笔记 -- 序列的基本
- 下一篇: Jenkins + Git + Mave