• 原生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            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                 {  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);
    

     

  • 相关阅读:
    SpringMVC获取参数的几种方式
    java实现邮箱发送邮件功能
    java实现屏幕截屏功能
    java生成验证码结合springMVC
    lodash实践之依据规则处理对象
    js深入之实现call、apply和bind
    js内存深入学习(二)
    js内存深入学习(一)
    web缓存策略之HTTP缓存大全
    Service Worker MDN英文笔记
  • 原文地址:https://www.cnblogs.com/ronffy/p/canvas-bubble.html
Copyright © 2020-2023  润新知