摘要:前言初學,做了一個畫板應用,地址點這里。本篇為的一些基礎思想和注意事項,不是基礎。主要是在于事件上的實踐經驗屏兼容屏會使用多個物理像素渲染一個獨立像素,導致一倍圖在屏幕上模糊,也是這樣,所以我們應該把畫布的大小設為元素大小的或倍。
前言
初學canvas,做了一個畫板應用,地址點這里 。本篇為canvas的一些基礎思想和注意事項,不是基礎api。主要是在于touch事件上的實踐經驗
retina屏兼容retina屏會使用多個物理像素渲染一個獨立像素,導致一倍圖在retina屏幕上模糊,canvas也是這樣,所以我們應該把canvas畫布的大小設為canvas元素大小的2或3倍。元素大小在css中設置
</>復制代碼
const canvas = selector("#canvas")
const ctx = canvas.getContext("2d")
const RATIO = 3
const canvasOffset = canvas.getBoundingClientRect()
canvas.width = canvasOffset.width * RATIO
canvas.height = canvasOffset.height * RATIO
坐標系轉化
把相對于瀏覽器窗口的坐標轉化為canvas坐標,需要注意的是,如果兼容了retina,需要乘上devicePixelRatio。后面所有出現的坐標,都要通過這個函數轉化
</>復制代碼
function windowToCanvas (x, y) {
return {
x: (x - canvasOffset.left) * RATIO,
y: (y - canvasOffset.top) * RATIO
}
}
不得不提的是,《HTML5 Canvas核心技術》有一個相同的函數,但是書上那個是錯的(也有可能我看的那本是假書)
獲取touch點的坐標
</>復制代碼
function getTouchPosition (e) {
let touch = e.changedTouches[0]
return windowToCanvas(touch.clientX, touch.clientY)
}
畫布狀態的儲存和恢復
進行繪圖操作時,我們會頻繁設置canvas繪圖環境的屬性(線寬,顏色等),大多數情況下我們只是臨時設置,比如畫藍色的線段,又要畫一個紅色的正方形,為了不影響兩個繪圖操作,我們需要在每次繪制時,先保存環境屬性(save),繪圖完畢后恢復(restore)
</>復制代碼
ctx.save()
ctx.fillStyle = "#333"
ctx.strokeStyle = "#666"
ctx.restore()
繪制表面的儲存與恢復
主要用于臨時性的繪圖操作,比如用手指拖出一個方形時,首先要在touchstart事件里儲存拖動開始時的繪制表面(getImageData),touchmove的事件函數中,首先要先恢復touch開始時的繪圖表面(putImageData),再根據當前的坐標值畫出一個方形,繼續拖動時,剛才畫出的方形會被事件函數的恢復繪圖表面覆蓋掉,在重新繪制一個方形,所以無論怎么拖動,我們看到的只是畫了一個方形,下面是畫板demo中方形工具的類
</>復制代碼
// 工具基礎 寬度,顏色,是否在繪畫中,是否被選中
class Basic {
constructor (width = RATIO, color = "#000") {
this.width = width
this.color = color
this.drawing = false
this.isSelect = false
}
}
class Rect extends Basic {
constructor (width = RATIO, color = "#000") {
super(width, color)
this.startPosition = {
x: 0,
y: 0
}
this.firstDot = ctx.getImageData(0, 0, canvasWidth, canvasHeight)
}
begin (loc) {
this.firstDot = ctx.getImageData(0, 0, canvasWidth, canvasHeight) //在這里儲存繪圖表面
saveImageData(this.firstDot)
Object.assign(this.startPosition, loc)
ctx.save() // 儲存畫布狀態
ctx.lineWidth = this.width
ctx.strokeStyle = this.color
}
draw (loc) {
ctx.putImageData(this.firstDot, 0, 0) //恢復繪圖表面,并開始繪制方形
const rect = {
x: this.startPosition.x <= loc.x ? this.startPosition.x : loc.x,
y: this.startPosition.y <= loc.y ? this.startPosition.y : loc.y,
width: Math.abs(this.startPosition.x - loc.x),
height: Math.abs(this.startPosition.y - loc.y)
}
ctx.beginPath()
ctx.rect(rect.x, rect.y, rect.width, rect.height)
ctx.stroke()
}
end (loc) {
ctx.putImageData(this.firstDot, 0, 0)
const rect = {
x: this.startPosition.x <= loc.x ? this.startPosition.x : loc.x,
y: this.startPosition.y <= loc.y ? this.startPosition.y : loc.y,
width: Math.abs(this.startPosition.x - loc.x),
height: Math.abs(this.startPosition.y - loc.y)
}
ctx.beginPath()
ctx.rect(rect.x, rect.y, rect.width, rect.height)
ctx.stroke()
ctx.restore() //恢復畫布狀態
}
bindEvent () {
canvas.addEventListener("touchstart", (e) => {
e.preventDefault()
if (!this.isSelect) {
return false
}
this.drawing = true
let loc = getTouchPosition(e)
this.begin(loc)
})
canvas.addEventListener("touchmove", (e) => {
e.preventDefault()
if (!this.isSelect) {
return false
}
if (this.drawing) {
let loc = getTouchPosition(e)
this.draw(loc)
}
})
canvas.addEventListener("touchend", (e) => {
e.preventDefault()
if (!this.isSelect) {
return false
}
let loc = getTouchPosition(e)
this.end(loc)
this.drawing = false
})
}
}
橢圓的繪制方法(均勻壓縮法)
原理是在壓縮過的坐標系中繪制一個圓形,那看起來就是一個橢圓了。因為是通過拖動繪制橢圓,所以在我們拖動時,必然拖出了一個方形,那其實就是以方形的中心為圓心,較長邊的一半為半徑畫圓,這個圓要畫在壓縮過的坐標系中,壓縮比例就是較窄邊與較長邊的比,圓心的坐標也要根據壓縮比例做坐標變換,圓形工具類代碼如下
</>復制代碼
class Round extends Basic{
constructor (width = RATIO, color = "#000") {
super(width, color)
this.startPosition = {
x: 0,
y: 0
}
this.firstDot = ctx.getImageData(0, 0, canvasWidth, canvasHeight)
}
drawCalculate (loc) {
ctx.save()
ctx.lineWidth = this.width
ctx.strokeStyle = this.color
ctx.putImageData(this.firstDot, 0, 0) //恢復繪圖表面
const rect = {
width: loc.x - this.startPosition.x,
height: loc.y - this.startPosition.y
} // 計算方形的寬高(帶有正負值)
const rMax = Math.max(Math.abs(rect.width), Math.abs(rect.height)) // 選出較長邊
rect.x = this.startPosition.x + rect.width / 2 // 計算壓縮前的圓心坐標
rect.y = this.startPosition.y + rect.height / 2
rect.scale = {
x: Math.abs(rect.width) / rMax,
y: Math.abs(rect.height) / rMax
} // 計算壓縮比例
ctx.scale(rect.scale.x, rect.scale.y)
ctx.beginPath()
ctx.arc(rect.x / rect.scale.x, rect.y / rect.scale.y, rMax / 2, 0, Math.PI * 2)
ctx.stroke()
ctx.restore()
}
begin (loc) {
this.firstDot = ctx.getImageData(0, 0, canvasWidth, canvasHeight) //儲存繪圖表面
saveImageData(this.firstDot)
Object.assign(this.startPosition, loc)
}
draw (loc) {
this.drawCalculate(loc)
}
end (loc) {
this.drawCalculate(loc)
}
bindEvent () {
canvas.addEventListener("touchstart", (e) => {
e.preventDefault()
if (!this.isSelect) {
return false
}
this.drawing = true
let loc = getTouchPosition(e)
this.begin(loc)
})
canvas.addEventListener("touchmove", (e) => {
e.preventDefault()
if (!this.isSelect) {
return false
}
if (this.drawing) {
let loc = getTouchPosition(e)
this.draw(loc)
}
})
canvas.addEventListener("touchend", (e) => {
e.preventDefault()
if (!this.isSelect) {
return false
}
let loc = getTouchPosition(e)
this.end(loc)
this.drawing = false
})
}
}
撤銷操作
上述例子中都有個 saveImageData() 函數,這個函數是把當前繪圖表面儲存在一個數組中,點擊撤銷的時候用于恢復上一步的繪圖表面
</>復制代碼
const lastImageData = []
function saveImageData (data) {
(lastImageData.length == 5) && (lastImageData.shift()) // 上限為儲存5步,太多了怕掛掉
lastImageData.push(data)
}
document.getElementById("cancel").addEventListener("click", () => {
if(lastImageData.length < 1) return false
ctx.putImageData(lastImageData[lastImageData.length - 1], 0, 0)
lastImageData.pop()
})
總結
有一些看上去高大上的東西,了解了以后就會發現很簡單,有了基礎的模型以后,再去一點一點豐富功能,所以有些時候不能總是看看看,一定要動手,yeah
我的博客即將搬運同步至騰訊云+社區,邀請大家一同入駐:https://cloud.tencent.com/dev...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/82736.html
摘要:,算法就是這樣,那我們基于該算法再對現有代碼進行一次升級改造設置線條顏色在原有的基礎上,我們創建了一個變量用于保存之前事件中鼠標經過的點,根據該算法可知要繪制二次貝塞爾曲線起碼需要個點以上,因此我們只有在中的點數大于時才開始繪制。 背景概要 相信大家平時在學習canvas 或 項目開發中使用canvas的時候應該都遇到過這樣的需求:實現一個可以書寫的畫板小工具。 嗯,相信這對canva...
摘要:,算法就是這樣,那我們基于該算法再對現有代碼進行一次升級改造設置線條顏色在原有的基礎上,我們創建了一個變量用于保存之前事件中鼠標經過的點,根據該算法可知要繪制二次貝塞爾曲線起碼需要個點以上,因此我們只有在中的點數大于時才開始繪制。 背景概要 相信大家平時在學習canvas 或 項目開發中使用canvas的時候應該都遇到過這樣的需求:實現一個可以書寫的畫板小工具。 嗯,相信這對canva...
摘要:方法可以獲取到上下文二制作畫板畫板功能可以繪制不同顏色和粗細的線條,畫板上有橡皮擦功能,一鍵清除功能,下載功能。我們可以用來監聽三種狀態。 學習制作畫板之前,我們先來了解一下canvas標簽 一.canvas標簽 1.canvas標簽與img標簽相似,但是canvas標簽是一個閉合標簽,并且沒有src alt屬性2.canvas標簽有兩個屬性,width,height。我們在頁面上用c...
閱讀 1784·2023-04-25 14:33
閱讀 3386·2021-11-22 15:22
閱讀 2183·2021-09-30 09:48
閱讀 2693·2021-09-14 18:01
閱讀 1747·2019-08-30 15:55
閱讀 3009·2019-08-30 15:53
閱讀 2146·2019-08-30 15:44
閱讀 654·2019-08-30 10:58