当前位置: 首页 > Web前端 > vue.js

如何用Canvas给JDer的作品拍照

时间:2023-03-31 20:23:22 vue.js

背景在京东,工作五年的老员工被称为“大老板”。如果他们已经工作了十年,他们将被称为“超级老板”。从2016年5月19日起,每年的这一天被定为京东集团“519老员工节”。俗话说:五年磨银,十年锻金!在京东成长10年的员工,在行业内任何一家公司都可以金光闪闪!在这5年、10年的无数个日日夜夜的奋斗中,你是以怎样的姿态工作的?下面就来揭秘这些pose是怎么练出来的吧~怎么玩先用一张gif来回顾一下效果吧。在技??术选型中,我们可以看到这里使用了大量的图片。通过图片的拖动、缩放等操作,放置人物和饰品,最终合成出相应的图片。那么这个过程是如何实现的呢?首先,我们使用NUTUI搭建整个项目,它的脚手架可以很好的处理图片优化和打包。底部操作菜单模块使用NUTUI中的Tab组件,提高开发效率。主界面选择了基于canvas的creatjs库和轻量级触屏设备手势库hammer.js进行开发。NUTUINUTUI是一个京东风格的移动端组件库,开发和服务于移动Web界面的企业级前中后端产品。50+优质组件,40+京东移动项目在用,支持按需加载,支持服务端渲染(VueSSR)...快扫码体验吧Hammer.jsHammer开源代码可以识别手势的touch、mouse和pointerEvents的库。它没有任何依赖性,并且很小,压缩后只有7.34kB。支持常用的单点和多点触控手势,可以添加自定义手势Create.jsCreateJS是一套基于HTML5开发的模块化库和工具。基于这些库,可以非常快速地开发基于HTML5的游戏、动画和交互式应用程序。CreateJS包括以下部分。本项目主要使用EaseJs,结合Tween.js制作一些小动画。了解了所用到的技术之后,我们来看看具体的实现过程:实现方案。本项目主要包括加载图片、绘制姿势、手势操作三个核心。下面分别讨论一下。1.加载图片由于本项目99%的模块都是由图片组成的,所以预加载图片的功能必不可少。图片那么多,需要我手动一张一张列出来加载吗?当然不是!现在是机械化时代,能交给工具的就不做。constfs=require("fs");constpath=require("path");letcomponents=[];constfiles=fs.readdirSync(path.resolve(__dirname,"../img/"));文件.forEach(function(item){components.push(`'@/asset/img/${item}'`);});letdata=`letimgList=[${[...components]}]module.exports=imgList;`;fs.writeFile(path.resolve(__dirname,"./imgList.js"),data,(error)=>{console.log(error);});依赖nodejs对文件的读写,完成图片列表文件的自动生成,加载时依次加载这个列表下的图片。2.画姿EaselJS承接了Createjs中的“画图”能力,这里使用了画图和画文字的API。EaselJS一般的绘制步骤是:创建舞台->创建对象->设置对象属性->在舞台上添加对象->更新舞台呈现下一帧this.stage=newcreatejs.Stage(this.帆布);//创建一个舞台letbgImg=newcreatejs.Bitmap(imgSrc);//创建对象this.stage.addChild(bgImg);//添加对象到stageCreateJs提供了两种渲染模式,一种是使用setTimeout,一种是使用requestAnimationFrame,默认是setTimeout,帧数是20。这里我们选择requestAnimationFrame模式,因为有很多对页面元素的操作,这种方法会更流畅。createjs.Ticker.timingMode=createjs.Ticker.RAF;//RAF是requestAnimationFrame的缩写createjs其他基本设置easeljs事件默认不支持触摸设备,需要手动开启createjs.Touch.enable(this.stage);实时刷新阶段createjs.Ticker.addEventListener("tick",this.stage.update(event));hammer.js配置由于hammer.js默认不开启rotate事件,需要在options中使用recognizers设置一个recognizerletbodyHandle=newHammer.Manager(this.canvas,{recognizers:[[Hammer.Rotate],[Hammer.Pan]],});让bodyRotate=newHammer.Rotate();bodyHandle.add(bodyRotate);准备工作完成,下面正式开始绘制场景。为了保持文明形象,不支持站在办公桌上。因此将场景分为背景和桌子两部分,通过在角色上层设置桌子的层级来设置约束条件。先画背景letBg=newImage();Bg.src=require("../asset/img/scene"+n+".png");Bg.onload=()=>{让bgimg=newcreatejs.Bitmap(Bg);this.stage.addChild(bgimg);};注意,如果不是第一次绘制,需要清除之前的内容this.stage.removeAllChildren();以同样的方式绘制表格,需要注意的是表格绘制完成后,需要设置它的level...this.stage.addChild(deskImg);this.stage.setChildIndex(deskImg,1);绘制人物绘制人物不同于场景,这里需要用到Container。Container是一个容器,可以包含Text、Bitmap、Shape、Sprite等EaselJS元素。例如,您可以将手臂、腿、躯干和头部放在一起,将它们转换成一个组,同时仍然相对于彼此移动这些部分。这里我们将人物及其表情放在一个Container中进行统一管理,统一移动、缩放、旋转等。在绘制角色之前,我们先确定绘制位置:默认位置在画布中间letpos={x:this.canvasW/2,y:this.canvasH/2,};如果字符已经被选中,需要替换,需要保持上一个字符的位置pos={x:joy.x,y:joy.y,};下面是具体的绘制步骤:varjoy=newImage();joy.src=require("../asset/img/joy"+n+".png");//加载人物图片joy.onload=()=>{varjoyImg=newcreatejs.Bitmap(joy);//创建图像joyImg.name="joy";//给角色命名joyImg.regX=joy.width/2;//将x方向移动到中心点joyImg.regY=joy.height/2;//将y方向移动到中心点joyImg.x=pos.x;//设置初始位置joyImg.y=pos.y;//设置初始位置letcontainer=newcreatejs.Container();//创建容器container.name="joyContainer";//容器名称container.addChild(joyImg);//添加角色到容器this.stage.addChild(container);//添加一个容器到舞台};画表情在上面画人物的时候,会创建一个容器,名字叫joyContainer,我们也把表情画在里面varface=newcreatejs.Bitmap(imgBg);...joyContainer.addChild(face);这样,当我们要移动角色时,我们可以通过移动容器来保证完整性。否则,头部将跟不上身体的运动。..删除元素从添加角色开始,会记录当前操作对象activeItem。当删除按钮被触发时,只要找到activeItem并删除其相关内容即可。constele=this.stage.getChildByName(this.activeItem.name);this.stage.removeChild(ele);3。手势操作hammer.js是一个用于检测触摸手势的JavaScript库,支持最常见的单点和多点Point-to-touch手势,并且完全可扩展以添加自定义手势。该功能将集成在NUTUI中,并在下个版本正式发布。bodyHandle.on("rotate",(e)=>{letctrEle=this.activeItem;ctrEle.scaleX=ctrEle.scaleY=e.scale*this.nowScale;ctrEle.rotation=this.BorderBox.rotation=e.rotation+this.nowRotate;});通过监听rotate事件,可以得到当前操作的缩放和旋转数据,我们可以结合之前的状态来实现各种手势操作效果。好了,万事俱备,开始表演吧~先选个办公场景,然后来个角色扮演,站着有点累?没关系,换个姿势坐下就好了,当然,你想站在凳子上也没关系。.表达方式是不是有点老套?然后伸出你的舌头。在电脑水杯的排列上,最后有一句标语,“在京东胖20斤”。.你玩得开心吗?好吧,不着急,我们继续说说如何实现吧。生成图片点击“完成”后,我们将进入分享页面。分享页面的底图是从三种颜色中随机选择的。这里我们需要创建一个临时的canvas来绘制分享的图片,将分享的背景、自定义的pose场景图(通过canvas.toDataURL方法转换成图片)、二维码、昵称依次绘制到这个临时的canvas中,最后导出图片后分配给分享图片的url。让tmpStage=newcreatejs.Stage(tmpCanvas);tmpStage.addChild(bg,share,code,text);由于分享图片和分享页面显示元素不完全相同,所以展示给用户的是分享页面,而分享图片如果透明度设置为0,则只能保存,看不到。然而,事情并没有那么简单,一大波bug正在源源不断的袭来。.路由遇到的问题底部导航被移除。前面说了这个项目由加载页面和主界面两个页面组成,中间是通过路由(历史模式)进行跳转。但是在某些手机中,通过路由跳转到另一个页面时,底部会自动出现导航模块。这是我们不想看到的。在已经被拉伸的空间里,不可能有这么大一块。公差是存在的。因此权衡之后选择了替换模式,但是进入主界面后,用户无法返回到加载页面,鱼和熊掌不可兼得。ios中的输入框是不会自动收回的。加载完成后,有一个白色的方块,里面有一个昵称的输入框。ios下输入完成后,关闭键盘后页面底部会出现一大片空白,卡死。但是当我们在页面上随意滑动时,这个白色块就会消失。这是因为在ios键盘弹起后,整个页面会被往上推,所以我们需要在blur键盘落下时使用scrollTo函数滚动页面,让页面回到原来的位置。blur(){window.scrollTo(0,0);}自从系统更新后,白色的方块变成了透明的,让人更加难以揣摩了。明明什么都看不到,但是输入框无法选中。不要以为脱下马甲就不认识你了,上面的解决方法还是有效的。图片跨域本地开发完成。将代码上传到服务器后,原本的世界一片安静美好,取而代之的是一片耀眼的红色:经过一番研究,我发现了下面这段话:虽然可以在没有CORS认证的情况下在画布中使用,但是这样做会污染画布。一旦画布被污染,就无法再从画布中提取数据。例如,canvas的toBlob()、toDataURL()或getImageData()方法不能再使用;这样做会引发安全错误。这可以防止用户在未经许可的情况下通过使用图像从远程网站获取信息来暴露隐私数据。这就解释了上面错误的由来,那么如何解决呢?varbg=newImage();bg.crossOrigin="Anonymous";这样就可以在图片加载的时候开启CORS功能,从而绕过报错。点击报错图片可以加载,但是当我想做拖拽操作的时候,又报错了。..createjs提供了hitArea命中区域。另一个对象objB可以设置为显示对象objA的hitArea。当点击objB时,相当于点击了objA。这个objB不需要添加到显示对象列表中,也不需要可见,但是它会在交互事件的触发中代替objA。varhitArea=newcreatejs.Shape();hitArea.graphics.beginFill("#000").drawRect(0,0,imgBg.width,imgBg.height);//这里的size是图片大小,img请自行调整.hitArea=hitArea;给物体绑定一个命中区域,这样拖拽就是操作这个区域而不是原来的图片,这样就不会报错了。在本项目的设置中,角色位于所有其他元素的最下方,选择元素时,也需要将当前选中的元素置于最上方。这里使用了createjs的setChildIndex方法。setChildIndex方法允许您向上或向下移动显示对象在显示列表中的位置。显示列表可以看做是一个数组,它的索引位置从0开始。如果创建了3个元素,那么它们的位置分别是0、1、2层。2层的对象在外面,0层的对象在里面。如果你想将一个元素移动到所有元素的最前面,那么你需要使用getNumChildren属性,它表示容器中显示的对象数量。最外层的深度是第numChildren-1层。对于原始级别高于top元素的其他元素,相应级别将降低一级。if(ele.name==="joy"){this.stage.setChildIndex(ele,1);}else{this.stage.setChildIndex(ele,this.stage.getNumChildren()-2);}在我们选择的或者在添加元素时,触发level设置,因为要保证当前操作的元素level在最上面。因为有顶层元素,所以在设置层级时,如果是人物元素,则设置在第二层,只高于场景的背景层;如果是其他元素,则将其设置为第二顶层。低版本ios的base64onload有问题。测试阶段发现ios10以下手机不能拖拽。真是晴天霹雳!在调查的过程中,我发现了一些奇怪的事情。我无法拖放,因为复选框上的删除按钮没有加载。这个按钮有什么特别之处?哦,原来是webpack配置中的url-loader自动将小图片转成base64格式添加的。按照这个思路,去掉这个功能后,问题就解决了,只是我们没有深究。接下来的结果更惨,分享的图片消失了,只剩下一个背景框!上面“生成图片”一节提到,图片都是通过toDataURL从canvas中导出的??,导出的格式正是出现上述问题的base64格式。我们发现在ios10以下版本base64无法触发onload事件,而是报错了。那么base64图片可以转成什么格式呢?答案在这里:dataURLToBlob(dataurl){//dataurl:data:image/webp;base64,UklGRvAIAABXRUJQVlA4WAoAAAAQAAAAXwAAXwAAQUxQSC4CAAABkAXbtmlH+xmxn...vararr=dataurl.split(',');//['数据:图像/webp;base64','UklGRvAIAABXRUJQVlA4WAoAAAAQAAAAXwAAXwAAQUxQSC4CAAABkAXbtmlH+xmxn...']varmime=arr[0].match(/:(.*?);/)[1];//单独的mime类型-->image/webpvarbstr=atob(arr[1]);//atob()方法用于对base64编码的字符串进行解码,转换为字符串中存储的原始二进制数据。varn=bstr.length;varu8arr=newUint8Array(n);//Uint8Array表示一个8位无符号整型数组,创建时内容初始化为0。创建后,您可以将数组中的元素作为对象或使用数组下标来引用。while(n--){u8arr[n]=bstr.charCodeAt(n);//依次存储Unicode码}returnnewBlob([u8arr],{type:mime});//type:表示将要放入blob中的数组内容的MIME类型}我们先将base64图片转成blob格式sharePhoto.src=window.URL.createObjectURL(this.dataURLToBlob(photo));然后通过URL.createObjectURL方法生成ObjectURLwindow.URL。revokeObjectURL(sharePhoto);由于createObjectURL返回的url一直保存在内存中,直到文档触发卸载事件(例如:文档关闭)。所以大家养成一个好习惯,用完记得释放哦~那么createObjectURL究竟有何神圣之处呢?一起来学习一下:createObjectURL定义:URL.createObjectURL()方法会根据传入的参数创建一个指向参数对象的URL。URL只存在于创建它的文档中。新对象URL指向执行的文件对象或Blob对象。createObjectURL返回一个带有hash的url,保存在内存中,直到文档触发卸载事件(例如:文档关闭)或执行revokeObjectURL释放。浏览器支持如下,移动端基本可以放心使用~防止长按事件即将上线,因为内部app还没有完全支持长按保存图片,所以暂时决定屏蔽掉这个在其中发挥作用。以下是尝试的三种方法:添加一个透明的div以覆盖顶层。由于长按保存时间是在img标签上触发的,div可以在触发touchstart时屏蔽contextmenu的本质。长按触发contextmenu上下文菜单,所以我们只需要阻止这个事件document.oncontextmenu=(e)=>{e.preventDefault();};在网页浏览器中有效,但在移动端无效加上样式*{-webkit-touch-callout:none;/*系统默认菜单被禁用*/-webkit-user-select:none;/*webkit浏览器*/-moz-user-select:none;/*Firefox*/-ms-user-select:none;/*IE10*/用户选择:无;/*用户是否可以选择文字*/}实践证明这种方法不可行,我们依次分析一下:user-select控制用户是否可以选择文字,这里我们需要的是控制图片.-webkit-touch-callout:当您触摸并按住触摸目标时,抑制或显示系统默认菜单。适用于:新窗口打开等link元素,保存图片等img元素,乍一看,这不就是我们需要的吗?但是,-webkit-touch-callout是一个不受支持的WebKit属性,它没有出现在CSS规范草案中。只看支持情况:最后选择了第一种方法,简单直接,没有考虑兼容性。图像优化解决了以上一系列问题之后,就要回到最初的分析:无论项目中使用什么技术,最终呈现的本质都是图像。因此,图片的大小不仅会影响加载速度,还会影响渲染速度。为了提供更好的用户体验,选择使用NUTUI中的图片压缩功能,可以提供高压缩比图片优化,并且可以自动转换成webp格式。大家都知道webp格式的图片比普通的压缩图片要小很多。有这么强大的后盾,想不出众都难!总结无论你是大佬、超级大佬,还是刚加入京东的新鲜血液,519老员工节都是每个京东人共同的节日!不断试错,不断解决问题,学习新知识,收获颇丰。在以后的工作中,还要注意基础知识的广度,不断积累。也许学习的时候不清楚应用场景,但总有一天我们会发现,每一种知识都有它存在的理由。