基於刮刮卡Vue元件釋出一個NPM包

嘻嘻哈哈學習發表於2019-03-16

一、前言

專案中需要實現一個刮刮卡的模組,專案結束後沉澱專案時剛好可以把刮刮卡模組封裝好,在下次新的專案中要用到時,可以更好的提高專案的效率,當然也更好地提供給其他小夥伴使用。

原始碼地址:github.com/ZENGzoe/vue… npm包地址:www.npmjs.com/package/vue…

刮刮卡元件的效果如下:

基於刮刮卡Vue元件釋出一個NPM包

二、刮刮卡vue元件的實現

刮刮卡功能的實現可以分三步:

1. 工作流的搭建

工作流使用的是vue-cliwebpack-simple模版,能夠滿足元件基本的編譯要求:

vue init webpack-simple vue-scratch-card
複製程式碼

執行後,根據元件錄入package.json的資訊。

Use sass? (y/N) y
複製程式碼

在專案這裡我選擇的是use sass

src目錄下建立packages目錄,用於存放所有的子元件,在本元件中只有一個刮刮卡元件,因此在packages裡新建scratch-card目錄用於存放我們的刮刮卡元件。如果還有其他子元件,可以繼續在packages新增子元件,最終目錄如下:

.
├── 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:

基於刮刮卡Vue元件釋出一個NPM包

因此需要修改我們的配置檔案webpack.config.js

//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目錄。

2.canvas實現刮刮卡

刮刮卡主要是通過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頁面繫結的事件為mousesdownmousemovemouseup,移動端頁面中繫結的事件為touchstarttouchmovetouchend,因此需要區分在不同端的事件繫結。

//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的具體實現,可以檢視原始碼。

刮刮卡擦拭的效果由CanvasglobalCompositeOperation屬性實現,設定globalCompositeOperation = "destination-out"讓手指或滑鼠與Canvas畫布重合區域不可見,就可以讓刮刮卡的擦拭效果。在touchmovemousemove繫結的事件中新增擦拭效果。實現如下:

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();
    }
}
複製程式碼

每次手指或滑鼠離開時,為了不汙染其他區域的事件和佔用內容,要清除繫結的事件。

那麼到這裡就實現了刮刮卡的所有邏輯,接下來就是要將刮刮卡元件封裝成外掛。

3.封裝成外掛

將VUE元件封裝成外掛,就應該有一個公開的install方法,這樣才可以通過Vue.use()呼叫外掛。詳細介紹可以看VUE的官方文件

scratch-card目錄中新建index.js,用來封裝scratchCardinstall方法:

//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

釋出到npm前,需要修改package.json,設定"private":true,否則npm會拒絕釋出私有包。除此之外,還需要新增入口檔案,"main":"dist/vue-scratch-card.js",可以在當requireimport包時載入模組。

//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,最後終於釋出成功了,太不容易了~_~

相關文章