你們好,我是六六。在學習裁剪功能的過程當中,發現有不少文章講的不是那麼清晰易懂,讓六六繞了不少彎路,因此今天呢,爲了讓你們再也不繞彎,六六要詳詳細細的手把手教你們寫一個裁剪功能出來。web
裁剪功能的核心之中固然是canvas這個技術,若是不懂的能夠去mdn上過一遍這個,不用看的太深,瞭解它是幹啥的,怎麼幹就好了。以個人大白話說,就是在座標系內可以操做每一個像素點的。接下來,咱們先來了解一下關於裁剪核心的api和相關知識:算法
用法:繪製一張圖片到canvas元素裏面
image:image對象或者是canvas對象
x,y:爲座標的起始點
實例:
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
var img = new Image();
img.src = 'images/backdrop.png'; // 圖片地址
img.onload = function(){
ctx.drawImage(img,0,0); // 拿到image對象,畫入canvas上
}
}
其實把,這個api有九個參數,藉助官網的圖:
複製代碼
用法:返回一個imageData對象,包含一個data像素數組,一個width爲寬,一個height爲高的
複製代碼
關於data數據(以個人大白話理解來講,專業說法在上面),其實就是描述每一個像素點的信息
(數據很是大),咱們都知道rgba吧,前三個數字表明像素點的顏色,第四個數字表明透明度,
因此data數據呢,就是整個選取部分的像素點信息的集合。每一個像素點有四個值,
依次push到data數據中。
複製代碼
如上圖所示,咱們就知道第一個像素點的顏色就是rgba(114,112,113,255/255),那麼他的位置
就是位於座標(0,0)。假如選取的canvas大小爲500\*500的,
那麼這個data數組的大小就是500\*500\*4。
複製代碼
用法:在場景中寫入像素數據
myImageData:就是imageData對象
dx,dy:就是場景的座標起點
複製代碼
用法:方法創造Blob對象,用以展現canvas上的圖片;這個圖片文件能夠被緩存或保存到本地
callback:回調函數,可得到一個單獨的Blob對象參數
type:DOMString類型,指定圖片格式,默認格式爲image/png。
encoderOptions :Number類型,值在0與1之間,當請求圖片格式爲image/jpeg
或者image/webp時用來指定圖片展現質量。若是這個參數的值不在指定類型與範圍以內,
則使用默認值,其他參數將被忽略。
複製代碼
首先咱們知道裁剪功能是須要運動,那麼確定會用到動畫。
h5提供了咱們一個終極動畫解決的函數,就是requestanimationframe.
複製代碼
思路很容易懂,接下來咱們就來一步一步實現。上面的每一個api必須熟練掌握,不熟悉的回頭再看一看。下面就是代碼加gif圖片演示,基本上每句代碼都是有備註的。canvas
思路:
首先經過input元素咱們能夠獲取到img對象
,在圖片加載出來後就能夠畫入畫圖中,
並能夠循環動畫。
複製代碼
<template>
<div id="app">
<canvas width='500' height='500' id='canvas'></canvas>
<input type="file"
@change="handleChange"
multiple='true'
accept='image/*'
>
</div>
</template>
<script>
export default{
data(){
return{
ctx:'',
img:''
}
},
mounted(){
// 獲取canvas對象
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
// 講ctx存入data中
this.ctx=ctx
},
methods:{
// 讀取圖片
handleChange (e) {
const that = this
// 建立一個文件讀取流
const reader = new FileReader()
reader.readAsDataURL(e.target.files[0])
// 文件加載完成後能夠讀入
reader.onload = function () {
// this.reslut 爲圖片信息,就開始調用drawImg方法將圖片寫入canvas中
that.drawImg(this.result)
}
},
// 建立一個圖片對象 畫到畫布上
drawImg (imgData) {
const that = this
const img = new Image()
img.src = imgData
this.img = img
img.onload = function () {
// 循環動畫
window.requestAnimationFrame(that.animate)
}
},
animate(){
// 清除畫布,在下一次循環會畫入從新
this.ctx.clearRect(0,0,500,500)
// 畫照片
this.ctx.drawImage(this.img, 0, 0, this.img.width, this.img.height, 0, 0, 500, 500)
// 循環動畫
window.requestAnimationFrame(this.animate)
}
}
}
</script>
<style scoped>
canvas{
position: absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%);
border:1px solid red
}
</style>
複製代碼
思路:藉助三個事件,鼠標按下,移動,擡起。在移動事件
裏面經過offsetX和offsetY獲取鼠標在canvas內的座標
。而後建立一個函數根據鼠標的座標用來畫出這個裁剪框,
放入animate函數裏面循環。
複製代碼
演示: 後端
<template>
<div id="app">
<canvas width='500' height='500' id='canvas'></canvas>
<input type="file"
@change="handleChange"
multiple='true'
accept='image/*'
>
</div>
</template>
<script>
/* eslint-disable */
export default{
data(){
return{
ctx:'',
img:'',
rectLeft:150,
rectTop:150,
rectWidth:200,
rectHeight:200
}
},
mounted(){
const that=this
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
this.ctx=ctx
// 新增----綁定點擊事件,根據鼠標移動座標畫出裁剪框
this.down=canvas.addEventListener('mousedown',()=>{
canvas.onmousemove=function(e){
let x=e.offsetX
let y=e.offsetY
x <= 100 ? that.rectLeft=0: that.rectLeft = x - 100
y <= 100 ? that.rectTop =0: that.rectTop = y - 100
x >= 400 ? that.rectLeft = 300 : that.rectLeft = x - 100
y >= 400 ? that.rectTop = 300 : that.rectTop = y - 100
if(x<=100){
that.rectLeft=0
}
if(y<=100){
that.rectTop=0
}
}
})
canvas.addEventListener('mouseup',()=>{
console.log('擡起了')
canvas.onmousemove=null
})
},
methods:{
handleChange (e) {
const that = this
const reader = new FileReader()
reader.readAsDataURL(e.target.files[0])
reader.onload = function () {
that.drawImg(this.result)
}
},
drawImg (imgData) {
const that = this
const img = new Image()
img.src = imgData
this.img = img
img.onload = function () {
window.requestAnimationFrame(that.animate)
}
},
animate(){
this.ctx.clearRect(0,0,500,500)
this.ctx.drawImage(this.img, 0, 0, this.img.width, this.img.height, 0, 0, 500, 500)
// 新增---畫裁剪框
this.drawRect()
window.requestAnimationFrame(this.animate)
},
// 新增----畫出裁剪框
drawRect(){
this.ctx.beginPath()
this.ctx.lineWidth = 2
this.ctx.strokeStyle = 'rgba(0,0,0,0.6)'
this.ctx.strokeRect(this.rectLeft, this.rectTop, this.rectWidth, this.rectHeight)
}
}
}
</script>
<style scoped>
canvas{
position: absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%);
border:1px solid red
}
input{
position: absolute;
top:10%;
left:50%;
transform: translate(-50%,-50%);
}
</style>
複製代碼
爲了更好的提升用戶體驗感,不得不得進行視覺優化:
思路:在每次循環動畫的開始,咱們能夠先獲取裁剪框
內的imageData,就是獲取照片原色彩,
以後經過算法使用putImageData方法將整個canvas對象色彩變成灰色,
而後再把以前獲取
裁剪框內的彩色在使用putImageData繪製上去便可。
複製代碼
<template>
<div id="app">
<canvas width='500' height='500' id='canvas'></canvas>
<input type="file"
@change="handleChange"
multiple='true'
accept='image/*'
>
</div>
</template>
<script>
/* eslint-disable */
export default{
data(){
return{
ctx:'',
img:'',
rectLeft:150,
rectTop:150,
rectWidth:200,
rectHeight:200,
chooseRgb:[]
}
},
mounted(){
const that=this
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
this.ctx=ctx
this.down=canvas.addEventListener('mousedown',()=>{
canvas.onmousemove=function(e){
let x=e.offsetX
let y=e.offsetY
x <= 100 ? that.rectLeft=0: that.rectLeft = x - 100
y <= 100 ? that.rectTop =0: that.rectTop = y - 100
x >= 400 ? that.rectLeft = 300 : that.rectLeft = x - 100
y >= 400 ? that.rectTop = 300 : that.rectTop = y - 100
if(x<=100){
that.rectLeft=0
}
if(y<=100){
that.rectTop=0
}
}
})
canvas.addEventListener('mouseup',()=>{
console.log('擡起了')
canvas.onmousemove=null
})
},
methods:{
handleChange (e) {
const that = this
const reader = new FileReader()
reader.readAsDataURL(e.target.files[0])
reader.onload = function () {
that.drawImg(this.result)
}
},
drawImg (imgData) {
const that = this
const img = new Image()
img.src = imgData
this.img = img
img.onload = function () {
window.requestAnimationFrame(that.animate)
}
},
animate(){
this.ctx.clearRect(0,0,500,500)
this.ctx.drawImage(this.img, 0, 0, this.img.width, this.img.height, 0, 0, 500, 500)
this.drawChoose()
this.drawHui()
this.drawRect()
window.requestAnimationFrame(this.animate)
},
drawRect(){
this.ctx.beginPath()
this.ctx.lineWidth = 2
this.ctx.strokeStyle = 'rgba(0,0,0,0.6)'
this.ctx.strokeRect(this.rectLeft, this.rectTop, this.rectWidth, this.rectHeight)
},
// 新增----獲取裁剪框的色彩色彩
drawChoose () {
const data = this.ctx.getImageData(this.rectLeft, this.rectTop, this.rectWidth, this.rectHeight)
this.chooseRgb = data
},
//新增---- 所有圖片變灰色而且畫上彩色的
drawHui () {
const data = this.ctx.getImageData(0, 0, 500, 500)
for (let i = 0; i < data.data.length; i += 4) {
const grey = (data.data[i] + data.data[i + 1] + data.data[i + 2]) / 3
data.data[i] = data.data[i + 1] = data.data[i + 2] = grey
}
// 將變成灰色的像素繪製上去
this.ctx.putImageData(data, 0, 0)
// 將彩色的裁剪框繪製上去
this.ctx.putImageData(this.chooseRgb, this.rectLeft, this.rectTop)
},
}
}
</script>
<style scoped>
canvas{
position: absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%);
border:1px solid red
}
input{
position: absolute;
top:10%;
left:50%;
transform: translate(-50%,-50%);
}
</style>
複製代碼
思路:須要建立一個新的canvas對象
,隨後調用putImageData將裁剪的像素對象畫上去便可
複製代碼
<template>
<div id="app">
<canvas width='500' height='500' class='canvas1'></canvas>
<!-- 新增的canvas元素-->
<canvas width='200' height='200' class='canvas2'></canvas>
<input type="file"
@change="handleChange"
multiple='true'
accept='image/*'
>
<button @click='caijian'>點擊裁剪</button>
</div>
</template>
<script>
/* eslint-disable */
export default{
data(){
return{
ctx:'',
img:'',
rectLeft:150,
rectTop:150,
rectWidth:200,
rectHeight:200,
chooseRgb:[]
}
},
mounted(){
const that=this
const canvas = document.querySelector('.canvas1')
const ctx = canvas.getContext('2d')
this.ctx=ctx
this.down=canvas.addEventListener('mousedown',()=>{
canvas.onmousemove=function(e){
let x=e.offsetX
let y=e.offsetY
x <= 100 ? that.rectLeft=0: that.rectLeft = x - 100
y <= 100 ? that.rectTop =0: that.rectTop = y - 100
x >= 400 ? that.rectLeft = 300 : that.rectLeft = x - 100
y >= 400 ? that.rectTop = 300 : that.rectTop = y - 100
if(x<=100){
that.rectLeft=0
}
if(y<=100){
that.rectTop=0
}
}
})
canvas.addEventListener('mouseup',()=>{
console.log('擡起了')
canvas.onmousemove=null
})
},
methods:{
handleChange (e) {
const that = this
const reader = new FileReader()
reader.readAsDataURL(e.target.files[0])
reader.onload = function () {
that.drawImg(this.result)
}
},
drawImg (imgData) {
const that = this
const img = new Image()
img.src = imgData
this.img = img
img.onload = function () {
window.requestAnimationFrame(that.animate)
}
},
animate(){
this.ctx.clearRect(0,0,500,500)
this.ctx.drawImage(this.img, 0, 0, this.img.width, this.img.height, 0, 0, 500, 500)
this.drawChoose()
this.drawHui()
this.drawRect()
window.requestAnimationFrame(this.animate)
},
drawRect(){
this.ctx.beginPath()
this.ctx.lineWidth = 2
this.ctx.strokeStyle = 'rgba(0,0,0,0.6)'
this.ctx.strokeRect(this.rectLeft, this.rectTop, this.rectWidth, this.rectHeight)
},
drawChoose () {
const data = this.ctx.getImageData(this.rectLeft, this.rectTop, this.rectWidth, this.rectHeight)
this.chooseRgb = data
},
drawHui () {
const data = this.ctx.getImageData(0, 0, 500, 500)
for (let i = 0; i < data.data.length; i += 4) {
const grey = (data.data[i] + data.data[i + 1] + data.data[i + 2]) / 3
data.data[i] = data.data[i + 1] = data.data[i + 2] = grey
}
this.ctx.putImageData(data, 0, 0)
this.ctx.putImageData(this.chooseRgb, this.rectLeft, this.rectTop)
},
// 新增一個canvas元素 用來存儲裁剪的部分,以及上傳時須要建立這個元素。
caijian(){
const canvas=document.querySelector('.canvas2')
const ctx = canvas.getContext("2d")
canvas.width = 200
canvas.height = 200
ctx.putImageData(this.chooseRgb, 0, 0)
}
}
}
</script>
<style scoped>
.canvas1{
position: absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%);
border:1px solid red
}
.canvas2{
position: absolute;
top:50%;
left:75%;
transform: translate(-50%,-50%);
border:1px solid red
}
input{
position: absolute;
top:10%;
left:50%;
transform: translate(-50%,-50%);
}
button{
position: absolute;
top:10%;
left:70%;
transform: translate(-50%,-50%);
}
</style>
複製代碼
思路:就是經過canvas.toBlob這個api實現的,
下面代碼是我以前上傳到服務器的時候寫的,因爲如今木有服務器,
就不能演示了。
複製代碼
// 須要接受canvas元素對象,上一部分建立的
uploadImg (canvas) {
// 異步獲取blob對象
canvas.toBlob(async (blob) => {
// 實例化一個FormData
let fd = new FormData()
fd.append('file', blob)
fd.name = new Date().getTime()
// 傳送數據
const result = await this.$http({
method: 'post',
url: 'api/users/upload',
headers: {
'Content-Type': 'multipart/form-data'
},
data: fd
})
if (result.data.success) {
this.isShow = false
this.$bus.$emit('on1', result.data.url)
this.$alert.success('更換頭像成功', 1000)
}
}, 'image/png')
}
}
複製代碼
整個裁剪過程已經講完了,上傳那一塊仍是有疑惑的同窗,能夠參考個人這篇文章api
Vue先後端開發仿蘑菇街商城項目 這裏面有上傳頭像的先後端代碼,你們能夠去參考一下。仍是有疑惑的能夠給我留言哈。數組
這個月末以前,我爭取開發一下裁剪的插件,共你們使用,畢竟造輪子仍是頗有趣的。但願你們都能來捧場~緩存