項目中須要實現一個刮刮卡的模塊,項目結束後沉澱項目時恰好能夠把刮刮卡模塊封裝好,在下次新的項目中要用到時,能夠更好的提升項目的效率,固然也更好地提供給其餘小夥伴使用。html
源碼地址:github.com/ZENGzoe/vue… npm包地址:www.npmjs.com/package/vue…vue
刮刮卡組件的效果以下:node
刮刮卡功能的實現能夠分三步:webpack
工做流使用的是vue-cli
的webpack-simple
模版,可以知足組件基本的編譯要求:git
vue init webpack-simple vue-scratch-card
複製代碼
執行後,根據組件錄入package.json的信息。github
Use sass? (y/N) y
複製代碼
在項目這裏我選擇的是use sass
。web
在src
目錄下建立packages
目錄,用於存放全部的子組件,在本組件中只有一個刮刮卡組件,所以在packages
裏新建scratch-card
目錄用於存放咱們的刮刮卡組件。若是還有其餘子組件,能夠繼續在packages
添加子組件,最終目錄以下:vue-cli
.
├── README.md
├── index.html
├── node_modules
├── package-lock.json
├── package.json
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ ├── main.js //組合全部子組件,封裝組件
│ ├── main.js //入口文件
│ └── packages //用於存放全部的子組件
│ └── scratch-card //用於存放刮刮卡組件
│ └── scratch-card.vue //刮刮卡組件代碼
└── webpack.config.js
複製代碼
爲了支持組件可使用標籤<script>
的方式引入,封裝成組件的打包文件只須要統一打包爲js:npm
所以須要修改咱們的配置文件webpack.config.js
:json
//webpack.config.js
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'vue-scratch-card.js',
library : 'vue-scratch-card', //設置的是使用require時的模塊名
libraryTarget : 'umd', //libraryTarget能夠設置不一樣的umd代碼,能夠是commonjs標準、amd標準,也能夠生成經過script標籤引入的
umdNamedDefine : true, //會對UMD的構建過程當中的amd模塊進行命名,不然就用匿名的define
},
複製代碼
同時,爲了保留打包的dist
目錄,須要在.gitignore
中去掉dist
目錄。
刮刮卡主要是經過Canvas
實現,通常刮刮卡是和抽獎結合,那麼咱們的DOM
應該要包含能夠顯示抽獎結果的DOM
,結構以下:
//scratch-card.vue
<template>
<div :id='elementId' class='scratchCard'>
<div class="result" v-show='showLucky'>
<slot name='result'></slot>
<img :src="resultImg" alt="" class="pic" />
</div>
<canvas id='scratchCanvas'></canvas>
</div>
</template>
複製代碼
其中,添加了一個<slot>
插槽,爲了能夠在調用這個組件時,定製抽獎結果的DOM
。
接下來是實現刮刮卡的邏輯部分。
大體js結構以下:
//scratch-card.vue
export default {
name : 'vueScratchCard',
data(){
return {
supportTouch : false, //是否支持touch事件
events : [], //touch事件 or mouse事件合集
startMoveHandler : null, //touchstart or mousedown 事件
moveHandler : null, //touchmove or mousemove 事件
endMoveHandler : null, //touchend or mouseend 事件
showLucky : false, //顯示隱藏抽獎結果
firstTouch : true, //是否第一次touchstart or mousedown
}
},
props : {
elementId : { //組件最外層DOM的id屬性
type : String,
default : 'scratch',
},
moveRadius : { //刮刮範圍
type : Number,
default : 15
},
ratio : { //要求刮掉的面積佔比,達到這個佔比後,將會自動把其他區域清除
type : Number,
default : 0.3
},
startCallback : { //第一次刮回調函數
type : Function,
default : function(){
}
},
clearCallback : { //達到ratio佔比後的回調函數
type : Function ,
default : function(){
}
},
coverColor : { //刮刮卡遮罩顏色
type : String,
default : '#C5C5C5'
},
coverImg : { //刮刮卡遮罩圖片
type : String,
},
resultImg : { //中獎的圖
type : String,
default : 'https://raw.githubusercontent.com/ZENGzoe/imagesCollection/master/2018/default.jpg'
}
},
mounted : function(){
},
methods : {
}
}
複製代碼
開始編寫邏輯以前,須要考慮組件可配置屬性,添加到props
中,讓組件的使用可以更加靈活。
在組件掛載到實例中時,開始初始化組件,繪製Canvas
。
//scratch-card.vue
...
mounted : function(){
this.$nextTick(() => {
this.init();
})
},
methods : function(){
init : function(){
if(!this.isSupportCanvas){
alert('當前瀏覽器不支持canvas');
return;
}
const canvasWrap = document.getElementById(this.elementId);
this.canvas =canvasWrap.querySelector('#scratchCanvas');
this.ctx = this.canvas.getContext('2d');
this.canvas.width = canvasWrap.clientWidth;
this.canvas.height = canvasWrap.clientHeight;
this.createCanvasStyle();
},
createCanvasStyle : function(){
var _this = this;
//當傳入coverImg時,優先使用圖片,不然使用顏色做爲刮刮卡遮罩
if(this.coverImg){
var coverImg = new Image();
coverImg.src = this.coverImg;
coverImg.onload = function(){
_this.ctx.drawImage(coverImg , 0 , 0 , _this.canvas.width , _this.canvas.height);
}
}else{
_this.ctx.fillStyle = _this.coverColor;
_this.ctx.fillRect(0,0,_this.canvas.width , _this.canvas.height);
}
},
}
...
複製代碼
當須要向Canvas
添加跨域圖片時,須要將圖片轉爲base64。
PC頁面綁定的事件爲mousesdown
、mousemove
、mouseup
,移動端頁面中綁定的事件爲touchstart
、touchmove
、touchend
,所以須要區分在不一樣端的事件綁定。
//scratch-card.vue
...
init : function(){
...
this.bindEvent();
},
bindEvent : function(){
if('ontouchstart' in window) this.supportTouch = true;
this.events = this.supportTouch ? ['touchstart', 'touchmove', 'touchend'] : ['mousedown', 'mousemove', 'mouseup'];
this.addEvent();
},
...
複製代碼
爲了減小篇幅,綁定事件addEvent
的具體實現,能夠查看源碼。
刮刮卡擦拭的效果由Canvas
的globalCompositeOperation
屬性實現,設置globalCompositeOperation = "destination-out"
讓手指或鼠標與Canvas
畫布重合區域不可見,就可讓刮刮卡的擦拭效果。在touchmove
和mousemove
綁定的事件中添加擦拭效果。實現以下:
moveEventHandler : function(e){
e.preventDefault();
e = this.supportTouch ? e.touches[0] : e;
const canvasPos = this.canvas.getBoundingClientRect(),
scrollT = document.documentElement.scrollTop || document.body.scrollTop,
scrollL = document.documentElement.scrollLeft || document.body.scrollLeft,
//獲取鼠標或手指在canvas畫布的位置
mouseX = e.pageX - canvasPos.left - scrollL,
mouseY = e.pageY - canvasPos.top - scrollT;
this.ctx.beginPath();
this.ctx.fillStyle = '#FFFFFF';
this.ctx.globalCompositeOperation = "destination-out";
this.ctx.arc(mouseX, mouseY, this.moveRadius, 0, 2 * Math.PI);
this.ctx.fill();
},
複製代碼
每次手指或鼠標離開時,計算擦拭區域,當擦去的區域大於畫布的約定的百分比時,清除整個Canvas
畫布。擦拭區域的計算至關於計算畫布上的像素點是否還有數據,經過getImageData
方法可獲取畫布的像素點。具體實現以下:
caleArea : function(){
let pixels = this.ctx.getImageData(0,0,this.canvas.width,this.canvas.height),
transPixels = [];
pixels.data.map((item , i) => {
const pixel = pixels.data[i+3];
if(pixel === 0){
transPixels.push(pixel);
}
})
if(transPixels.length / pixels.data.length > this.ratio){
this.ctx.clearRect(0,0,this.canvas.width , this.canvas.height);
this.canvas.removeEventListener(this.events[0],this.startMoveHandler);
this.canvas.removeEventListener(this.events[1] , this.moveHandler , false);
document.removeEventListener(this.events[2] , this.endMoveHandler , false);
this.clearCallback();
}
}
複製代碼
每次手指或鼠標離開時,爲了避免污染其餘區域的事件和佔用內容,要清除綁定的事件。
那麼到這裏就實現了刮刮卡的全部邏輯,接下來就是要將刮刮卡組件封裝成插件。
將VUE組件封裝成插件,就應該有一個公開的install
方法,這樣才能夠經過Vue.use()
調用插件。詳細介紹能夠看VUE的官方文檔。
在scratch-card
目錄中新建index.js
,用來封裝scratchCard
的install
方法:
//scratch-card/index.js
import vueScratchCard from './scratch-card'
vueScratchCard.install = Vue => Vue.component(vueScratchCard.name , vueScratchCard);
if(typeof window !== 'undefined' && window.Vue){
window.Vue.use(vueScratchCard);
}
export default vueScratchCard;
複製代碼
到這裏咱們封裝好了咱們的子組件刮刮卡,若是有其餘子組件,能夠繼續往packages
目錄中添加,最後在src
目錄下新建index.js
,組合全部的子組件。
//src/index.js
import VueScratchCard from './packages/scratch-card/index.js';
const install = function(Vue , opts = {}){
Vue.component(VueScratchCard.name , VueScratchCard);
}
//支持使用標籤<script>的方式引入
if(typeof window !== 'undefined' && window.Vue){
install(window.Vue);
}
export default {
install ,
VueScratchCard
}
複製代碼
這樣就完成了咱們的組件啦~~
發佈到npm
前,須要修改package.json
,設置"private":true
,不然npm
會拒絕發佈私有包。除此以外,還須要添加入口文件,"main":"dist/vue-scratch-card.js"
,能夠在當require
或import
包時加載模塊。
//package.json
"private": false,
"main" : "dist/vue-scratch-card.js",
複製代碼
npm發佈流程以下:
1.在npm註冊帳號
2.登錄npm,須要將鏡像地址改成npm
npm login
複製代碼
3.添加用戶信息,輸入帳號密碼
npm adduser
複製代碼
4.發佈
npm publish
複製代碼
發佈成功後,就能夠在npm搜索到發佈的包。
以後咱們就能夠在直接安裝使用了~~
安裝:
npm install vue-scratch-card0 -S
複製代碼
使用:
1.經過import使用
import ScratchCard from 'vue-scratch-card0'
Vue.use(ScratchCard)
<vue-scratch-card
element-id='scratchWrap'
:ratio=0.5
:move-radius=50
/>
複製代碼
2.經過標籤<script>
引用
<vue-scratch-card
element-id='scratchWrap'
:ratio=0.5
:move-radius=50
:start-callback=startCallback
:clear-callback=clearCallback
cover-color='#caa'
/>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
new Vue({
el : '#app',
data () {
return {
}
},
methods : {
startCallback(){
console.log('抽獎成功!')
},
clearCallback(){
console.log('清除完畢!')
}
}
})
</script>
複製代碼
發佈npm包時,一直提示Package name too similar to existing packages
,可是在npm官網中,又沒有查到命名相同的包,這個時候就不停的換package.json
中的name,最後終於發佈成功了,太不容易了~_~