原生js實現canvas氣泡冒泡效果


說明: 

本文章主要分為ES5和ES6兩個版本

ES5版本是早期版本,后面用ES6重寫優化的,建議使用ES6版本。

 

1, 原生js實現canvas氣泡冒泡效果的插件,api豐富,使用簡單
2, 只需引入JumpBubble.js一個js文件即可

 

項目源碼地址: https://github.com/roonly/JumpBubble

== 使用demo:

 

ES6版本的使用demo:

const bubble = new JumpBubble(document.getElementById(`cavs`));

bubble.create('/img/fish.png');

 

ES5代碼的時候demo:

var demo = new JumpBubble({
  elCanvas : document.getElementById("canvasIdName")
});
demo.create({
  elImg : document.getElementById("imgIdName")
});

 

== 效果:

 

== html 代碼:

 

ES6版本:

//html內容
<canvas id="cavs" width="130" height="400" style="border:1px solid #fff;">您的瀏覽器不支持canvas標簽~</canvas> //index.js內容:
let list = [
	'http://p4.cdn.btime.com/t01e430315c854b44d2.png',
	'http://p5.qhimg.com/t017f9904d4be818a87.png',
	'http://p5.qhimg.com/t015ec16e404a442dd4.png',
	'/img/fish.png', //注:路徑是相對html的路徑,因為該路徑最終會放到img標簽的src上
];
const bubble = new JumpBubble(document.getElementById(`cavs`));
setInterval(() => {
	if(s > list.length - 1){
		s = 0;
	}
	bubble.create(list[s]);
	s++
},250);

 

ES5版本:

<!DOCTYPE HTML>
<html>
<meta charset="utf-8">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>原生js實現canvas氣泡冒泡效果</title>
<body>
<p style="display:none">
  <img id="img1" src="http://p8.qhimg.com/t01053ab4d4d6510abd.png" alt="">
  <img id="img2" src="http://p5.qhimg.com/t017f9904d4be818a87.png" alt="">
  <img id="img3" src="http://p5.qhimg.com/t015ec16e404a442dd4.png" alt="">
  <img id="img4" src="http://p6.qhimg.com/t017895dcd6312beacb.png" alt="">
  <img id="img5" src="http://p2.qhimg.com/t01f70bccf10e16addd.png" alt="">
  <img id="img6" src="http://p3.qhimg.com/t016d419cab67d819ac.png" alt="">
</p>
<h2>小賊說:原生js實現canvas氣泡冒泡效果的插件,api豐富,使用簡單</h2>
<canvas id="myCanvas" width="250" height="430" style="border:1px solid #eee">您的瀏覽器不支持canvas標簽~</canvas>
<canvas id="myCanvas2" width="200" height="330" style="border:1px solid #eee">您的瀏覽器不支持canvas標簽~</canvas>
<script src="JumpBubble.js"></script> 
<script type="text/javascript">
window.onload = function(){
  // 使用demo
  var demo = new JumpBubble({
    elCanvas : document.getElementById("myCanvas")
  });
  clearInterval(setDemo1);
  var setDemo1 = setInterval(function(){
    var idName = "img" + Math.ceil(Math.random()*6);
    demo.create({
      elImg : document.getElementById(idName)
    });
  },150);

  // demo2
  var demo2 = new JumpBubble({
    elCanvas : document.getElementById("myCanvas2"),
    config : {
      alpha : 0.5,
      width : 30
    },
    callback : function(a,b,c){}
  });
  clearInterval(setDemo2);
  var setDemo2 = setInterval(function(){
    demo2.create({
      elImg : document.getElementById("img1")
    });
  },150);

}

</script>
</body>
</html> 

 

 

== javascript 代碼:(分為ES6版 和 ES5版)

 

ES6版:

 

  1 /*
  2 ** @param canvasNode   [必傳] <DOM> canvan標簽元素
  3 ** @param config       [選傳] <Obj> 可選配置項
  4 **    - effect      <Str> 氣泡的y軸浮動效果,可選2個值:linear、ease,默認'ease'
  5 **    - speed       <Str> 氣泡的Y軸移動速度,可選3個值 slow default fast, 默認'default'
  6 **    - isToAlapha  <Boo> 氣泡是否逐漸增加透明度 默認true
  7 **    - alpha       <Num> 初始氣泡的透明度, 默認0.9
  8 **    - max                    <Num> //畫布上最多同時存在多少氣泡,默認30,請根據氣泡出現頻道調整此值,已達到最優的視覺效果
  9 **    - diffWidth   <Num> 初始冒泡時氣泡寬度與指定寬度(width屬性值)的差值,默認15,差值越大,冒泡時氣泡從小變大的效果越明顯
 10 **    - left        <Num> 冒泡位置,距離畫布左側像素數,默認在畫布中間靠左15像素位置
 11 **    - top         <Num> 冒泡位置,距離畫布頂部像素數,默認距離畫布頂部為畫布高度減30像素
 12 **    - width       <Num> 氣泡的寬度, 默認30,為保證氣泡不變形,高度隨寬度改變
 13 ** @param  callback    [選傳] <Func> 態度氣泡實例初始化后的回調
 14 */
 15 export default class JumpBubble{
 16     constructor(canvasNode, config = {}, callback ){
 17         if(!canvasNode || !canvasNode.getContext){
 18           console.warn("jumpBuffle,啟用失敗,canvas傳參錯誤 或 瀏覽器不支持canvas");
 19           this.error = true;
 20           return false;
 21         }
 22         const t = this,
 23             { width, height } = canvasNode;
 24         const _config = { //配置氣泡冒泡設置
 25           width: 30, //可自定義氣泡寬度,高度隨寬度變化
 26           left : width/2 - 15,  //距離左側距離
 27           top : height - 30, //距離頂部距離
 28           alpha : 0.9,  // 透明度設置
 29           effect: 'ease',
 30           speed: 'default',
 31           max: 30, //畫布上最多同時存在多少氣泡, 默認30
 32           isToAlapha: true,
 33           diffWidth: 15, // 初始冒泡時氣泡寬度與正常寬度的差值,默認15
 34           cavHeight: height,  // [非配置項] canvan標簽的高度,設置氣泡在不同高度有不同的浮動速度時會用到
 35           cavWidth: width,      // [非配置項] canvan標簽的高度,設置氣泡在左右晃動,觸壁反彈時會用到
 36         };
 37         Object.assign(t, {
 38             canvasInfo: {
 39                 canvas : canvasNode,
 40                 width,
 41                 height
 42             },
 43             config: Object.assign(_config, config),
 44             ctx: canvasNode.getContext("2d"),
 45             bubbleArr: [],//用來存儲所有的氣泡
 46             allImg: { //緩存創建的img標簽
 47                 lists: [], //src的列表
 48                 doms: [], //緩存list對應的創建的dom
 49                 loadState: [], //是否img標簽已經加載完畢,如果完畢再直接返回img標簽的dom,如果沒有加載完畢則放到img.load函數內返回imgdom
 50             }
 51         });
 52         callback && callback(t);
 53     }
 54     /*
 55     ** 冒泡的生命周期
 56     ** before: 氣泡開始冒泡前
 57     ** after: 單個氣泡消失后
 58     */
 59     create(imgsrc, before, after){
 60         const t = this,
 61                     {ctx, error} = t;
 62         if(!ctx || error){
 63           console.warn("jumpBuffle:create時,ctx錯誤");
 64           return false;
 65         }
 66         const { bubbleArr, canvasInfo } = t,
 67                 { width: imgwidth, max } = t.config;
 68         t.createImg(imgsrc).then(imgNode => {
 69             const imgInfo = {
 70               el : imgNode,
 71               width : imgwidth || imgNode.width,
 72               height : imgwidth && imgNode.height*(imgwidth/imgNode.width) || imgNode.height
 73             };
 74             if(bubbleArr.length > max){
 75               return;
 76             }
 77             bubbleArr.push(new DrawImg(ctx, imgInfo, t.config));
 78             //每添加一個氣泡觸發一次的回調函數, 生命周期為氣泡開始冒泡前
 79             before && before(canvasInfo.canvas, imgNode, bubbleArr);  
 80             if(!t.setInter){
 81               t.setInterFn(after);
 82             }
 83         });
 84         return this;
 85     }
 86     createImg(imgsrc){
 87         return new Promise(res => {
 88             const { lists, doms, loadState } = this.allImg;
 89             const i = lists.indexOf(imgsrc);
 90             if(i > -1 && loadState[i]){
 91                 res(doms[i]);
 92                 return ;
 93             }
 94             const img = document.createElement('img');
 95             img.src = imgsrc;
 96             img.setAttribute('style', 'display:none;');
 97             document.body.appendChild(img);
 98             lists.push(imgsrc);
 99             doms.push(img);
100             img.onload = () => {
101                 loadState.push(true);
102                 res(img);
103             }
104         })
105     }
106     setInterFn(after){
107         const t = this, 
108                     { ctx, canvasInfo } = t,
109                 { width, height } = canvasInfo;
110         t.setInter = setInterval(function(){
111           try{
112               ctx.clearRect(0, 0, width, height);
113               t.bubbleArr = t.bubbleArr.filter(function(val){
114                 val.addCtx();
115                 val.updateCtx();
116                 if(val.y < 10){
117                     after && after();
118                   return false;
119                 }else{
120                   return true;
121                 }
122               });
123               if(t.bubbleArr.length === 0){
124                 clearInterval(t.setInter);
125                 t.setInter = null;
126                 ctx.clearRect(0, 0, width, height);
127               }
128           }catch(e){
129               console.warn('創建態度氣泡出錯',e);
130               clearInterval(t.setInter);
131           }
132         },25);
133     }
134 }
135 
136 class DrawImg{
137     constructor(ctx, imgInfo, { left, top, alpha, speed, cavWidth, cavHeight, effect, isToAlapha, diffWidth }){
138         Object.assign(this, {
139             whichUnit: null, // 標識氣泡在畫布中的位置 ,canvan畫布分成3個部分,2:中上、1:中、0:中下
140             ctx,
141             originWidth: imgInfo.width,
142             img: imgInfo.el,
143             imgWidth: imgInfo.width - diffWidth, //氣泡初始大小與指定氣泡大小的差值
144             imgHeight: imgInfo.height - diffWidth,
145             x: left, //氣泡在x軸的位置 相對左側
146             y: top, //氣泡在y軸位置 相對頂部
147             alpha,
148             speed,
149             effect,
150             isToAlapha,
151             cavWidth,
152             oneUnit: cavHeight/4, //將canvan畫布分成3個部分,中上、中、中下(中下占2/4,其他各占1/4)
153             toRight: (Math.random() > 0.5 ? false : true), //在氣泡左右晃動效果時,該屬性標識氣泡是向左晃動還是向右晃動。
154             xPx: Math.random()*2.5, //x軸氣泡每次位移像素數
155             yPx: null, //緩存氣泡在畫布y軸上每次位移的距離
156             yPxArr: null, //緩存y軸每次位移像素數的3個階段的值的數組
157             diffAlapa: null, //緩存氣泡在變為透明時每次增加的透明度
158         });
159         this.updateCtx = this.updateCtx.bind(this);
160         this.effectCommon = this.effectCommon.bind(this);
161     }
162     getSpeed(type = 'default'){
163         switch(type){
164             case 'slow':
165                 return 0.6;
166             case 'fast':
167                 return 1.4;
168             default:
169                 return 1;
170         }
171     }
172     addCtx(){
173         const p = this, 
174                 { ctx } = p;
175         ctx.save();
176         ctx.globalAlpha = p.alpha;
177         ctx.drawImage(p.img, p.x, p.y, p.imgWidth, p.imgHeight);
178         ctx.restore();
179     }
180     setImgWidth(){
181         const { originWidth, imgWidth } = this;
182         if(imgWidth < originWidth){//差值diffWidth的初始小氣泡,逐漸變大為指定氣泡的大小
183           this.imgWidth += 1;
184           this.imgHeight += 1;
185         }
186     }
187     setAlapa(){
188         const p = this,
189                     { y, isToAlapha, whichUnit } = p;
190         if(!isToAlapha)return false; //可自行配置,是否逐漸增加透明度
191         let diffAlapa = p.diffAlapa;
192         if(whichUnit === 2){ //氣泡在畫布中上部分時
193             if(!diffAlapa){
194                 p.diffAlapa = diffAlapa = p.countDiffAlapa();
195             }
196           if(p.alpha <= diffAlapa){
197             p.alpha = 0;
198           }else{
199             p.alpha -= diffAlapa;
200           }
201         }
202     }
203     countDiffAlapa(){
204         const { alpha, oneUnit, yPx } = this;
205         return (alpha + 0.1)/(oneUnit/yPx);
206     }
207     setYpx(){
208         const p = this,
209                     { y, oneUnit } = p;
210         let i;
211         // 根據態度氣泡在容器內到達高度不同,設置不同的速度
212         switch(true){ 
213             case y < oneUnit: //氣泡在畫布中上部分時
214                 i = 2;
215                 break;
216             case y > oneUnit && y < oneUnit*2: //氣泡在畫布的中部分時
217                 i = 1;
218                 break;
219             default: //氣泡在畫布的中下部分
220                 i = 0;
221         }
222         if(p.whichUnit !== i){
223             p.yPx = p.yPxArr[i];
224             p.whichUnit = i;
225         }
226         p.y -= p.yPxArr[i];
227     }
228     setYpxArr(){
229         if(this.yPxArr)return false;
230         this.yPxArr = this.countYpxArr(this.effect, this.getSpeed(this.speed));
231     }
232     countYpxArr(effect, speedNum = 1){//根據effect不同,設置氣泡在y軸上位移距離,數組包含3個值,分別表示在y軸的3個階段的位移距離
233         const basePx = 2;
234         const easeArr = [basePx-0.5, basePx, basePx+0.5];
235         const linearArr = [basePx, basePx, basePx];
236         switch(effect){
237             case 'ease':
238                 return mlt(easeArr);
239             case 'linear':
240                 return mlt(linearArr);
241             default:
242                 return mlt(easeArr);
243         }
244         function mlt(arr){
245             return arr.map(v => v*speedNum);
246         }
247     }
248     pullback(){//在x軸上,觸壁反彈效果,晃動的上升,
249         const p = this,
250                     { y, cavWidth, xPx, originWidth } = p;
251         // 控制氣泡左右晃動,觸壁反彈效果
252         switch(true){
253             case (p.x + originWidth >= cavWidth): //氣泡觸右側壁
254                 p.toRight = false;
255                 p.x -= xPx;
256                 break;
257             case p.x <= 2: //氣泡觸左側壁
258                 p.toRight = true;
259                 p.x += xPx;
260                 break;
261             case p.toRight:
262                 p.x += xPx;
263                 break;
264             default:
265                 p.x -= xPx;
266         }
267         this.effectCommon();
268     }
269     effectCommon(){
270         this.setYpxArr();
271         this.setYpx();
272         this.setAlapa();
273         this.setImgWidth();
274     }
275     updateCtx(){
276         this.pullback();
277     }
278 }

 

 

ES5版:

 

;(function(window){
  function JumpBubble(opt){
    var t = this,
        canvas = opt.elCanvas,
        canvasW = canvas.width,
        canvasH = canvas.height;
    if(!canvas){
      console.warn("jumpBuffle:new 實例時,canvas傳參錯誤");
      return;
    }
    t.canvasInfo = {
      canvas : canvas,
      width : canvasW,
      height : canvasH
    };
    var canvas = t.canvasInfo.canvas;
    if(!canvas.getContext){
      console.warn("jumpBuffle,啟用失敗,瀏覽器不支持canvas");
      return;
    }
    var config = { //配置氣泡冒泡設置
      left : canvasW/2 - 15,  //距離左側距離
      top : canvasH - 30, //距離頂部距離
      alpha : 0.9  // 透明度設置
      // width : 30 // 默認使用傳入圖片的實際寬高,可自定義氣泡寬度,高度隨寬度變化
    };
    t.callback = opt.callback; //每添加一個氣泡觸發一次的回調函數
    t.config = hrExtend(config,opt.config);
    t.ctx = canvas.getContext("2d");
    t.bubbleArr = []; //用來存儲所有的氣泡
  };
  
  JumpBubble.prototype.create = function(opt){
    var t = this,
        bubbleArr = t.bubbleArr,
        ctx = t.ctx,
        img = opt.elImg,
        config = t.config,
        cfgImgWidth = config.width,
        convasInfo = t.canvasInfo,
        callback = t.callback;
    if(!ctx){
      console.warn("jumpBuffle:create時,ctx錯誤");
      return;
    }
    var imgInfo = {
      el : img,
      width : cfgImgWidth || img.width,
      height : cfgImgWidth && img.height*(cfgImgWidth/img.width) || img.height
    };
    if(bubbleArr.length>30){
      return false;
    }
    bubbleArr.push(new drawImg(ctx,imgInfo,t.config,convasInfo));
    //每添加一個氣泡觸發一次的回調函數,
    // 參數1:canvas元素;參數2:傳入的圖片元素;參數3:當前存在的氣泡數組
    callback && callback(convasInfo.canvas,img,bubbleArr);  
    if(!t.setInter){
      t.setInterFn();
    }
  };

  JumpBubble.prototype.setInterFn = function(){
    var t = this,
        ctx = t.ctx,
        convasInfo = t.canvasInfo,
        canvasW = convasInfo.width,
        canvasH = convasInfo.height;
    t.setInter = setInterval(function(){
      ctx.clearRect(0,0,canvasW,canvasH);
      t.bubbleArr = t.bubbleArr.filter(function(val){
        val.addCtx();
        val.updateCtx();
        if(val.y < 10){
          return false;
        }else{
          return true;
        }
      });
      if(t.bubbleArr.length === 0){
        clearInterval(t.setInter);
        t.setInter = null;
        ctx.clearRect(0,0,canvasW,canvasH);
      }
    },25);
  };


  function drawImg(ctx,imgInfo,config ,canvasInfo){
    var p = this;
    p.ctx = ctx;
    p.imgInfo = imgInfo,
    p.img = imgInfo.el;
    p.imgWidth = imgInfo.width - 10;
    p.imgHeight = imgInfo.height - 10;
    p.x = config.left;
    p.y = config.top;
    p.alpha = config.alpha;
    p.canvasInfo = canvasInfo;
    p.ranX = (Math.random()*5 - 2.5)/2;
  }
  drawImg.prototype.addCtx = function(){
    var p = this,
        ctx = p.ctx;
    ctx.save();
    ctx.globalAlpha = p.alpha;
    ctx.drawImage(p.img,p.x,p.y,p.imgWidth, p.imgHeight);
    ctx.restore();
  }
  drawImg.prototype.updateCtx = function(){
    var p = this,
        canvasInfo = p.canvasInfo,
        afterRoad = canvasInfo.height/4,
        ranX = p.ranX;
    if(p.y < afterRoad){
      if(Math.random() > 0.5){
        p.x += ranX/2;
      }
      p.y -= 2.5;
      if(p.alpha <= 0.02){
        p.alpha = 0;
      }else{
        p.alpha -= 0.02;
      }
    }else if(p.y > afterRoad && p.y < afterRoad*2){
      p.x += ranX/2;
      p.y -= 3;
      p.alpha -= 0.01;
    }else{
      p.x += ranX;
      p.y -= 4;
    }
    if(p.imgWidth < p.imgInfo.width){
      p.imgWidth += 1;
      p.imgHeight += 1;
    }
  }

  function deepCopy(p,c){
    /*@param p [必選] [對象] 被克隆對象
    **c :[可選] p對象被克隆到c身上,c被改變
    **返回值為深度克隆后的c*/
    var c= c || {};
    for(var i in p){
        if (typeof p[i] === 'object') {
            c[i] = (p[i].constructor === Array) ? [] : {};
            arguments.callee(p[i],c[i]);
        } else {
            c[i] = p[i];
        }
    }
    return c;
  };
  // 至少傳入2個參數,傳入的參數都將會被深度復制,不會影響原對象,然后返回擴展后的新對象
  function hrExtend() { //擴展對象
    var args = arguments;
    if (args.length < 2) return;
    var temp = deepCopy(args[0]); //調用復制對象方法
    for (var n = 1; n < args.length; n++) {
      for (var i in args[n]) {
        temp[i] = args[n][i];
      }
    }
    return temp;
  }
  window.JumpBubble = JumpBubble;
})(window);

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM