实验目标:借助canvas把一张国际象棋棋子图片转换为一组适用于WebGL渲染的精灵动画图片,不借助其他图片处理工具,不引用其他库只使用原生js实现。

初始图片如下:

一、图片分割

将初始图片分割为六张大小相同的棋子图片

1、html舞台:

 1 DOCTYPE html>  2 <html lang="en">  3 <head>  4 <meta charset="UTF-8">  5 <title>处理棋子图片title>  6 head>  7 <body>  8 <canvas id="can_source" style="z-index: 1;top:2px;left:2px;position: absolute">canvas>  9 <canvas id="can_mask" style="z-index: 10;top:2px;left:2px;position: absolute">canvas> 10 <canvas id="can_maskbak" style="z-index: 1;top:2px;left:2px;position: absolute">canvas> 11 body> 12 <script> 13 script> 14 html>

这里准备了三张canvas画布,其中can_source是预览原图的画布,称为“源画布”;can_mask是悬浮在can_source上层的透明背景画布,用来绘制切割范围提示,称为“提示画布”;can_maskbak用来圈定切割范围(其实可以不显示它),称为“范围画布”。

2、分割流程:

 1 var can_source=document.getElementById("can_source");  2 var can_mask=document.getElementById("can_mask");  3 var can_maskbak=document.getElementById("can_maskbak");  4 var top_res;  5 var width=0,height=0;  6 window.onload=function(){  7 var img=new Image();  8 img.src="../../ASSETS/IMAGE/ICON/chesses.jpg";  9 img.onload=function(){ 10 width=img.width;//根据图片尺寸设置画布尺寸 11 height=img.height; 12 can_source.style.width=width+"px";//css尺寸 13 can_source.style.height=height+"px"; 14 can_source.width=width;//canvas像素尺寸 15 can_source.height=height; 16 var con_source=can_source.getContext("2d"); 17 con_source.drawImage(img,0,0);//显示原图 18 19 top_res=height+4+"px"; 20 can_maskbak.style.left=width+4+"px";//把这个圈定范围的画布放在右边,做对比 21 can_maskbak.style.width=width+"px"; 22 can_maskbak.style.height=height+"px"; 23 can_maskbak.width=width; 24 can_maskbak.height=height; 25 var con_maskbak=can_maskbak.getContext("2d"); 26 con_maskbak.fillStyle="rgba(0,0,0,1)";//填充完全不透明的黑色 27 con_maskbak.fillRect(0,0,width,height); 28 29 can_mask.style.width=width+"px"; 30 can_mask.style.height=height+"px"; 31 can_mask.width=width; 32 can_mask.height=height; 33 var con_mask=can_mask.getContext("2d"); 34 con_mask.fillStyle="rgba(0,0,0,0)"; 35 con_mask.fillRect(0,0,width,height); 36 //下面是具体的操作代码 37 //cutRect(40,10,120,240,256,256);//矩形切割 38 //cutRect(192,10,120,240,256,256); 39 //cutRect(340,10,120,240,256,256); 40 cutRect(33,241,120,240,256,256); 41 cutRect(200,241,120,240,256,256); 42 cutRect(353,241,120,240,256,256); 43  } 44 }

3、具体切割算法:

 1 //从一个画布上下文中剪切一块dataUrl  2 function cutRect(x,y,wid,hig,wid2,hig2)  3  {  4 //将矩形转换为路径,然后用更一般化的路径方法处理区域  5 var path=[{x:x,y:y},{x:x+wid,y:y},{x:x+wid,y:y+hig},{x:x,y:y+hig}];  6 var framearea=[x,y,wid,hig];//framearea是操作范围的边界,矩形切割则直接是矩形本身,多边形切割则应是多边形的外切矩形范围  7  cutPath(path,framearea,wid2,hig2);  8  9  } 10 function cutPath(path,framearea,wid2,hig2) 11  { 12 var len=path.length; 13 var con_mask=can_mask.getContext("2d"); 14 con_mask.strokeStyle="rgba(160,197,232,1)";//线框 15  con_mask.beginPath(); 16 for(var i=0;i) 17  { 18 var point=path[i]; 19 if(i==0) 20  { 21  con_mask.moveTo(point.x,point.y); 22  } 23 else { 24  con_mask.lineTo(point.x,point.y); 25  } 26 27  } 28 con_mask.closePath();//在提示画布中绘制提示框 29  con_mask.stroke(); 30 //con_mask.Path; 31 32 33 var con_maskbak=can_maskbak.getContext("2d"); 34  con_maskbak.beginPath(); 35 con_maskbak.fillStyle="rgba(0,255,0,1)"; 36 con_maskbak.lineWidth=0; 37 for(var i=0;i) 38  { 39 var point=path[i]; 40  con_maskbak.lineTo(point.x,point.y); 41  } 42  con_maskbak.closePath(); 43  con_maskbak.fill();//在范围画布中画出切割的范围(纯绿色) 44 45 var con_source=can_source.getContext("2d"); 46 var data_source=con_source.getImageData(framearea[0],framearea[1],framearea[2],framearea[3]);//获取源画布在操作范围内的像素 47 var data_maskbak=con_maskbak.getImageData(framearea[0],framearea[1],framearea[2],framearea[3]);//获取范围画布在操作范围内的像素 48 49 var can_temp=document.createElement("canvas");//建立一个暂存canvas作为工具,并不实际显示它。 50 can_temp.width=wid2||framearea[2];//设置暂存画布的尺寸,这里要把长方形的切图保存为正方形! 51 can_temp.height=hig2||framearea[3]; 52 var con_temp=can_temp.getContext("2d"); 53 con_temp.fillStyle="rgba(255,255,255,1)"; 54 con_temp.fillRect(0,0,can_temp.width,can_temp.height); 55 var data_res=con_temp.createImageData(framearea[2],framearea[3]);//建立暂存画布大小的像素数据 56 57 58 var len=data_maskbak.data.length; 59 for(var i=0;i//对于范围画布的每一个像素 60  { 61 if(data_maskbak.data[i+1]=255)//如果这个像素是绿色 62  { 63 data_res.data[i]=(data_source.data[i]);//则填充源画布的对应像素 64 data_res.data[i+1]=(data_source.data[i+1]); 65 data_res.data[i+2]=(data_source.data[i+2]); 66 data_res.data[i+3]=(data_source.data[i+3]); 67  } 68 else 69  { 70 data_res.data[i]=(255);//否则填充完全不透明的白色,注意不透明度通道在rgba表示中是0到1,在data表示中是0到255! 71 data_res.data[i+1]=(255); 72 data_res.data[i+2]=(255); 73 data_res.data[i+3]=(255); 74  } 75  } 76 con_temp.putImageData(data_res,(can_temp.width-framearea[2])/2,(can_temp.height-framearea[3])/2)//把填充完毕的像素数据放置在暂存画布的中间 77  console.log(can_temp.toDataURL());//以dataUrl方式输出暂存画布的数据 78 79 }

4、切割效果如下:

在控制台里可以找到以文本方式输出的图片数据:

对于小于2MB的图片数据,直接复制dataUrl粘贴到浏览器地址栏回车,即可显示完整图片,之后右键保存;对于大于2MB的图片数据则需把can_temp显示出来,之后右键保存。精灵动画的单帧图片一般较小,所以不考虑需要显示can_temp的情况。

最终获取的一张“兵”图片:

5、改进

其实canvas的path对象本身就有clip方法,可以用这个内置方法简化以上过程。

clip方法的文档:https://www.w3school.com.cn/tags/canvas_clip.asp

二、生成精灵动画

1、html舞台及准备代码:

 1 DOCTYPE html>  2 <html lang="en">  3 <head>  4 <meta charset="UTF-8">  5 <title>建立棋子的动画帧,添加一个图标样式title>  6 head>  7 <body>  8 <canvas id="can_back" style="z-index: 1;top:2px;left:2px;position: absolute">canvas>  9 <canvas id="can_back2" style="z-index: 1;top:2px;left:2px;position: absolute">canvas> 10 <canvas id="can_res" style="z-index: 1;top:2px;left:2px;position: absolute">canvas> 11 body> 12 <script> 13 var can_back=document.getElementById("can_back"); 14 var can_back2=document.getElementById("can_back2"); 15 var can_res=document.getElementById("can_res"); 16 var width=240,height=360; 17  window.onload=function(){ 18  console.log("程序开始") 19  can_back.style.width=width+"px"; 20  can_back.width=width; 21  can_back.style.height=height+"px"; 22  can_back.height=height; 23  can_back2.style.width=width+"px"; 24  can_back2.width=width; 25  can_back2.style.height=height+"px"; 26  can_back2.height=height; 27  can_back2.style.left=width+4+"px"; 28  can_res.style.top=height+4+"px"; 29 var img=new Image(); 30  img.src="../../ASSETS/IMAGE/ICON/bing.png";//256*256的图片 31  img.onload=function(){//动画帧生成代码 32  } 33 script> 34 html>

2、在can_back中为棋子添加“徽章”背景

添加后效果如下:

为棋子添加了一个形状和颜色渐变的徽章背景,徽章外为透明色,可以根据棋子所属的势力为徽章设置不同的主色调。算法首先判断can_back的像素点是否在棋子“兵”内,如果在棋子内则原样呈现,否则根据像素的位置计算像素的颜色,一种实现方法如下:

 1 var con_back=can_back.getContext("2d");  2 con_back.fillStyle="rgba(0,255,0,0)";  3 con_back.fillRect(0,0,width,height);  4 con_back.drawImage(img,(width-256)/2,(height-256)/2)  5  6 var data_back=con_back.getImageData(0,0,width,height);  7 //var len=data_back.length;  8 var r1=22/19;  9 var r2=22/51;  10 var p_light=255;//背景强度  11 var i_min=0,i_max=0;  12 //一行一行地处理像素  13 var data=data_back.data;  14 for(var i=0;i<360;i++)  15  {  16 var num_w=(Math.pow(110*110-(i-100)*(i-100)*r2*r2,0.5));  17 for(var j=0;j<240;j++)//对于这一行里的每一个像素  18  {  19 var index=(i*240+j)*4;  20 if(i<5||i>355)  21  {  22 data[index]=0;  23 data[index+1]=255;  24 data[index+2]=0;  25 data[index+3]=0;  26  }  27 else  28  {  29  30 if(i<100)  31  {  32 if(Math.abs(j-119.5)<((i-5)*r1))  33  {  34 if(data[index]+data[index+1]+data[index+2]>600||data[index+3]==0)//不是黑色或者完全透明  35  {  36 var b=127+128*(95-(i-5))/95;//保持红色为主色调  37 var b2=(p_light-b)/2;  38 data[index]=b;  39 data[index+1]=b2;  40 data[index+2]=b2;  41 data[index+3]=255;  42  }  43 else  44  {  45 data[index]=0;  46 data[index+1]=0;  47 data[index+2]=0;  48 data[index+3]=255;  49 if(i_min==0)  50  {  51 i_min=i;  52 i_max=i;  53  }  54 else  55  {  56 if(i>i_max)  57  {  58 i_max=i;  59  }  60  }  61  }  62  }  63 else  64  {  65 data[index]=0;  66 data[index+1]=255;  67 data[index+2]=0;  68 data[index+3]=0;  69  }  70  }  71 else  72  {  73 //if(Math.abs(j-119.5)  74 if(Math.abs(j-119.5)<num_w)  75  {  76 if(data[index]+data[index+1]+data[index+2]>600||data[index+3]==0)//不是黑色  77  {  78 var b=127+128*(255-(355-i))/255;  79 var b2=(p_light-b)/2;  80 data[index]=b;  81 data[index+1]=b2;  82 data[index+2]=b2;  83 data[index+3]=255;  84  }  85 else  86  {  87 data[index]=0;  88 data[index+1]=0;  89 data[index+2]=0;  90 data[index+3]=255;  91 if(i_min==0)  92  {  93 i_min=i;  94 i_max=i;  95  }  96 else  97  {  98 if(i>i_max)  99  { 100 i_max=i; 101  } 102  } 103  } 104  } 105 else 106  { 107 data[index]=0; 108 data[index+1]=255; 109 data[index+2]=0; 110 data[index+3]=0; 111  } 112  } 113  } 114  } 115  } 116 con_back.putImageData(data_back,0,0);
View Code

3、在can_back2为徽章中的棋子描边

为后面的环节做准备,给棋子的轮廓描一层rgb(1,1,1)颜色、2px宽度的边

1 var size_border=2; 2 var rgb_border={r:1,g:1,b:1}; 3 if(size_border>0)//为前景和背景的边界描边的算法? 4 {//-》为特定的两种颜色边界描边的算法!!!! 5 console.log("开始描绘边界"); 6 drawBorder(data,240,360,isColorOut,isColorIn,Math.floor(size_border/2),size_border,rgb_border); 7  }//参数:像素数据,宽度,高度,判断像素在描边内测的条件,判断像素在描边外侧的条件,描边的偏移,边宽,描边的颜色 8 var con_back2=can_back2.getContext("2d"); 9 con_back2.putImageData(data_back,0,0);

描边函数:

 1 function isColorOut(rgba)  2  {  3 if(rgba.r>127)  4  {  5 return true;  6  }  7 return false;  8  }  9 function isColorIn(rgba)  10  {  11 if(rgba.r==0&&rgba.g==0&&rgba.b==0)  12  {  13 return true;  14  }  15 return false;  16  }  17 //参数:像素数据,图片的宽度,图片的高度,”外部“的颜色(可以有多种),“内部的颜色”(可以有多种,但不应与arr_rgba1重复!!)  18 // ,决定把边画在内部还是外部的偏移(默认为0,画在中间?为正表示向内偏),边的宽度,边的颜色(认为完全不透明)  19 //使用xy的垂直遍历方法,另一种思路是让计算核沿着分界线移动《-绘制的更为平滑  20 //function drawBorder(data,width,height,arr_rgbaout,arr_rgbain,offset_inout,size_border,rgb_border)  21 //内外的颜色可能是渐变的!!所以在这里用返回布尔值的函数做参数!!!!而非固定颜色范围  22 function drawBorder(data,width,height,func_out,func_in,offset_inout,size_border,rgb_border)  23  {  24 //首先对于每一行像素  25 for(var i=0;i)  26  {  27 var lastRGBA={};  28 for(var j=0;j)  29  {  30 var index=(i*240+j)*4;  31 var RGBA={r:data[index],g:data[index+1],b:data[index+2],a:data[index+3]};  32 //if(!lastRGBA.r&&lastRGBA.r!=0)//如果是第一个像素  33 if(j==0)  34  {  35 lastRGBA=RGBA;//上一颜色  36 continue;  37  }  38 else  39  {  40 //if(isRGBAinArr(arr_rgbaout,lastRGBA)&&isRGBAinArr(arr_rgbain,RGBA))//在内外颜色的分界处(左侧)  41 if(func_out(lastRGBA)&&func_in(RGBA))//如果上一颜色应该在描边的外侧,同时当前颜色在描边的内侧  42  {  43 var os_left=Math.floor(size_border/2);//偏右  44 var os_right=size_border-os_left;  45 var j_left=j-os_left;  46 var j_right=j+os_right;  47 j_left+=offset_inout;  48 j_right+=offset_inout;  49 for(var k=j_left;k//修正偏右  50  {  51 if(k>=0&&k<width)  52  {  53 var index2=(i*240+k)*4;  54 data[index2]=rgb_border.r;  55 data[index2+1]=rgb_border.g;  56 data[index2+2]=rgb_border.b;  57 data[index2+3]=255;  58  }  59  60  }  61  }  62 //else if(isRGBAinArr(arr_rgbaout,RGBA)&&isRGBAinArr(arr_rgbain,lastRGBA))//在内外颜色的分界处(右侧)  63 else if(func_out(RGBA)&&func_in(lastRGBA))  64  {  65 var os_right=Math.floor(size_border/2);//偏左  66 var os_left=size_border-os_right;  67 var j_left=j-os_left;  68 var j_right=j+os_right;  69 j_left-=offset_inout;  70 j_right-=offset_inout;  71 for(var k=j_left+1;k<=j_right;k++)//修正偏左  72  {  73 if(k>=0&&k<width)  74  {  75 var index2 = (i * 240 + k) * 4;  76 data[index2] = rgb_border.r;  77 data[index2 + 1] = rgb_border.g;  78 data[index2 + 2] = rgb_border.b;  79 data[index2 + 3] = 255;  80  }  81  }  82  }  83  }  84 lastRGBA=RGBA;  85  }  86  87  }  88 //然后对于每一列像素  89 for(var i=0;i)  90  {  91 var lastRGBA={};  92 for(var j=0;j//对于这一列中的每个像素  93  {  94 var index=(j*240+i)*4;  95 var RGBA={r:data[index],g:data[index+1],b:data[index+2],a:data[index+3]};  96 //if(!lastRGBA.r&&lastRGBA.r!=0)//如果是第一个像素  97 if(j==0)  98  {  99 lastRGBA=RGBA; 100 continue; 101  } 102 else 103  { 104 //if(isRGBAinArr(arr_rgbaout,lastRGBA)&&isRGBAinArr(arr_rgbain,RGBA))//在内外颜色的分界处(左侧) 105 if(func_out(lastRGBA)&&func_in(RGBA)) 106  { 107 var os_up=Math.floor(size_border/2);//偏下 108 var os_down=size_border-os_up; 109 var j_up=j-os_down; 110 var j_down=j+os_right; 111 j_up+=offset_inout; 112 j_down+=offset_inout; 113 for(var k=j_up;k//不修正偏下 114  { 115 if(k>=0&&k<height) 116  { 117 var index2=(k*240+i)*4; 118 data[index2]=rgb_border.r; 119 data[index2+1]=rgb_border.g; 120 data[index2+2]=rgb_border.b; 121 data[index2+3]=255; 122  } 123 124  } 125  } 126 //else if(isRGBAinArr(arr_rgbaout,RGBA)&&isRGBAinArr(arr_rgbain,lastRGBA))//在内外颜色的分界处(右侧) 127 else if(func_out(RGBA)&&func_in(lastRGBA)) 128  {//下面应该是忘了改变量名 129 var os_right=Math.floor(size_border/2);//偏左 130 var os_left=size_border-os_right; 131 var j_left=j-os_left; 132 var j_right=j+os_right; 133 j_left-=offset_inout; 134 j_right-=offset_inout; 135 for(var k=j_left;k//修正偏左 136  { 137 if(k>=0&&k<height) 138  { 139 var index2 = (k * 240 + i) * 4; 140 data[index2] = rgb_border.r; 141 data[index2 + 1] = rgb_border.g; 142 data[index2 + 2] = rgb_border.b; 143 data[index2 + 3] = 255; 144  } 145  } 146  } 147  } 148 lastRGBA=RGBA; 149  } 150 151  } 152 }

这里横竖遍历所有像素,在棋子轮廓内外边界处绘制描边,算法细节可能较难以想象,建议亲自调试实验。使用这种方法绘制的描边可能比较粗糙。

4、为棋子建立不同状态的动画帧

这里以生命值变化为例:

用棋子“填充度”的降低表示棋子生命值的减少,图像生成算法如下:

 1 console.log("开始生成健康状态图片");  2 /*关于边界,因为纵向体现状态比例,所以最上边和最下边是必然存在的,用最上边和最下边之间的区域分割状态比例  3  ,然后再根据边框宽度画其他的普通边,考虑到空洞的情况,纵向和横向的普通边数量是不确定的  4  -》描边的操作应该在前一步进行!!??*/  5  6 i_min+=size_border;  7 i_max-=size_border;  8 var i_height=i_max-i_min;  9 //接下来把它画在1800*1800的图片上(设为2048*2048可能获得更高性能和清晰度,但要求每个单元图片尺寸也必须是2的整数次幂,比如256*256),分为横5竖5最多25个状态 10 /*can_res.style.width=2048+"px"; 11  can_res.width=2048; 12  can_res.style.height=2048+"px"; 13  can_res.height=2048;*/ 14 can_res.style.width=1800+"px"; 15 can_res.width=1800; 16 can_res.style.height=1800+"px"; 17 can_res.height=1800; 18 var con_res=can_res.getContext("2d"); 19 //return; 20 //var data=data_back.data; 21 for(var h=10;h>=0;h--)//健康度状态分十一个阶段递减 22  { 23 console.log("生成"+h+"/"+10+"的图片") 24 var int_x=Math.floor((10-h)%5); 25 var int_y=Math.floor((10-h)/5); 26 if(h==10) 27  { 28 con_res.putImageData(data_back,int_x*360+60,int_y*360); 29  } 30 else 31  { 32 var i_up=Math.floor(i_max-i_height*((h+1)/10));//i偏低,取像素整体偏上 33 var i_down=Math.floor(i_max-i_height*((h)/10)+1); 34 for(var i=i_up;i)//对于每一行像素 35  { 36 var j_left=0,j_right=0; 37 for(var j=0;j<240;j++) 38  { 39 var index=(i*240+j)*4; 40 if(data[index]==0&&data[index+1]==0&&data[index+2]==0) 41  { 42 if(j_left==0) 43  { 44 j_left=j; 45 data[index]=0; 46 data[index+1]=255; 47 data[index+2]=0; 48 data[index+3]=0;//将像素不透明度设为0 49  } 50 else 51  { 52 data[index]=0; 53 data[index+1]=255; 54 data[index+2]=0; 55 data[index+3]=0; 56 j_right=j; 57  } 58  } 59  } 60 /*if(j_right>0) 61  { 62  var index=(i*240+j_right)*4; 63  data[index]=0; 64  data[index+1]=0; 65  data[index+2]=0; 66  data[index+3]=255; 67  }*/ 68 69 70  } 71 //描边 72 73 con_res.putImageData(data_back,int_x*360+60,int_y*360); 74 //putImageData时完全透明的rgb通道将被丢弃??!! 75  } 76 77 78 }

5、添加“被破坏”动画帧

实现思路是在棋子上绘制不断增大的透明圆表示棋子的消逝,需要注意的是因为谷歌浏览器无法精确处理半透明计算,所以考虑到以后可能需要绘制半透明的“消逝圆”的情况,先用不透明绿色绘制消逝圆,然后统一把绿色替换为具有精确透明度的颜色。实现代码如下:

 1 //接下来添加5帧栅格式的退出动画  2 for(var h=1;h<=5;h++)  3  {  4 var int_x=Math.floor((10+h)%5);  5 var int_y=Math.floor((10+h)/5);  6 con_res.putImageData(data_back,int_x*360+60,int_y*360);  7 con_res.fillStyle="rgba(0,255,0,1)";//考虑到对半透明的检查,在show图片时可以先绘制一个绿屏背景!!  8 con_res.lineWidth=0;  9 for(var i=0;i<4;i++) 10  { 11 for(var j=0;j<6;j++) 12  { 13  con_res.beginPath(); 14 con_res.arc(int_x*360+60+30+i*60,int_y*360+30+j*60,6*h,0,Math.PI*2); 15 con_res.fill();//这个方法不能正常呈现a通道 16  } 17 18  } 19  } 20 //将绿幕换成透明 21 22 var data_res=con_res.getImageData(0,0,1800,1800);// 23 var len=1800*1800*4; 24 var datar=data_res.data; 25 for(var i=0;i) 26 {//这个循环内加断点会导致运算超时 27 if(datar[i]==0&&datar[i+1]==255&&datar[i+2]==0) 28  { 29 datar[i+1]=0; 30 datar[i+3]=0; 31  } 32  } 33 con_res.putImageData(data_res,0,0);

6、使用

经过前面的操作我们得到了棋子“兵”的精灵动画图片:

使用相同方法,我们可以得到其他五种棋子的精灵动画图片,或者添加更多的精灵动画帧。我们可以在Babylon.js之类WebGL引擎中使用这些精灵动画图片创建精灵动画,可以在这里找到Babylon.js的精灵动画文档:旧版文档:https://ljzc002.github.io/BABYLON101/14Sprites%20-%20Babylon.js%20Documentation.htm,新版文档:https://doc.babylonjs.com/divingDeeper/sprites/sprite_map_animations。(4.2版又有了很多新改变,也许要再次开始文档翻译工作了)


标签:canvas

网页小实验——用canvas生成精灵动画图片的更多相关文章

  1. input标签checkbox选中触发事件的方法

    1.方法一function checkboxOnclick(checkbox){ if ( checkbox.che......

  2. HTML5与CSS3知识点总结

    HTML常用标签总结 手摸手带你学CSS 好好学习,天天向上本文已收录至我的Github仓库DayDayUP:git......

  3. 网页小实验——用canvas生成精灵动画图片

    实验目标:借助canvas把一张国际象棋棋子图片转换为一组适用于WebGL渲染的精灵动画图片,不借助其他图片处理工具......

  4. HTML5中的Web Notification桌面通知

    一、传统的通知实现通知可以说是web中比较常见且重要的功能,私信、在线提问、或者一些在线即时通讯工具我们总是希望第一......

  5. 如何在vue中使用HTML 5 拖放API

    拖放 API 将可拖动元素添加到 HTML,使我们可以构建包含可以拖动的具有丰富 UI 元素的 Web 应用。在本文......

随机推荐

  1. vue element和nuxt的使用技巧分享

    1.element的时间选择提交的格式转化例如Fri Sep 07 2018 00:00:00 GMT+0800 (......

  2. JavaScript——深入了解this

    前言我曾以为func()其实就是window.func()function func(){console.log('......

  3. Vue使用Ref跨层级获取组件实例

    目录Vue使用Ref跨层级获取组件实例示例介绍文档目录结构安装vue-ref根组件自定义方法[使用provide和i......

  4. python生成二维码

    python生成二维码需要用到的包pip install qrcode 代码:import qrcode from ......

  5. C# 中的动态类型

    翻译自 Camilo Reyes 2018年10月15日的文章 《Working with the Dynamic ......

  6. 使用CSS的clip-path实现图片剪切效果

    最近有个业务需求:校对图片文本信息,如下图所示,当鼠标点击文本中某一行的时候,文本上会显示对应行图片同时左侧会显示对......

  7. JavaScript实现浏览器网页自动滚动并点击的示例代码

    1. 打开浏览器控制台窗口JavaScript通常是作为开发Web页面的脚本语言,本文介绍的JavaScript代码......

  8. ASP.NET Core 3.1 中间件

    参考微软官方文档 :https://docs.microsoft.com/zh-cn/aspnet/core/fun......

  9. 将不规则的Python多维数组拉平到一维的方法实现

    原始需求:例如有一个列表:l = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]希望把它转换成下......

  10. python里glob模块知识点总结

    之前遇到过一类问题,要求快速做文件搜索,当时小编找了很多内容,但是没有发现实现方法,突然看到glob模块便豁然开朗了......