- 调用getUserProfile接口开发者工具上会返回vi这些数据,手机上就只返回userinfo?
调用getUserProfile接口开发者工具上会返回vi和encryptedData数据,为什么手机调试模式 和 体验版上面都只返回了userinfo
2021-04-08 - 服务号订阅通知灰度测试
服务号模板消息能力的设计初衷,旨在帮助开发者实现及时通知,但存在一些问题,如: 1. 部分开发者在用户无预期的情况下,发送与用户无关的信息,对用户造成了骚扰。 2. 模板消息是用户触发后的通知消息,不支持营销类消息,不能满足部分业务需求。 为提升微信用户体验,我们开始灰度测试服务号订阅通知功能。 能力说明 开发者可在服务号图文消息、网页等场景设置订阅功能,用户自主订阅后,开发者可按需求下发一条对应的订阅通知。 [图片] 用户可在图文订阅通知 [图片] 用户可在网页订阅通知 灰度测试计划 服务号订阅通知功能即日上线,已认证的境内主体服务号可前往 MP 后台开通使用,详见说明。 1. 服务号订阅通知灰度测试期自2021年1月27日0:00至4月30日24:00,期间服务号模板消息可正常使用;灰度测试期结束后服务号订阅通知的策略将另行公布,届时以官方信息为准; 2. 开发者使用订阅通知功能时,需遵循运营规范,不可用奖励或其它形式强制用户订阅,不可下发与用户预期不符或违反国家法律法规的内容。具体可参考文档:《微信公众平台运营规范》 微信团队 2021年1月27日
2021-01-29 - 开发者工具那些你未必知道的console命令
build 编译 相当于点击“编译”按钮 [图片] preview 预览 [图片] upload 上传代码 [图片] cleanAppCache 清除应用缓存 清除完需要重新编译 [图片] showRequestInfo 查看请求过链接的信息 检查证书一大利器 [图片] showSystemInfo 显示当前开发者工具占用内存及其他信息 [图片] checkProxy 检测目标网址是否启用代理 [图片] openToolsLog 打开开发者工具日志目录 openPlugin 打开插件目录 openVendor 打开供应商目录 更多详细的命令使用 help 进行获取 [图片]
2019-11-08 - 我们是怎么进行前端工程化的
随着业务的不断发展,企鹅电竞的前端规模在不断的疯狂膨胀。越来越多的项目被建立起来,越来越多的体系被架构出来,越来越多的代码变的越来越难维护。我们在不断创造新代码的时候,不断在思考,为啥我们的项目看起来这么的野?我们不禁反复思考,我们干了那么久的前端工程师,我们他喵的到底在干个啥?很自然,有人就开始说,我们要推动前端工程化,那么我们在讨论前端工程化的时候,我们到底在讨论啥? 文章将阐述我们在前端工程化的一些思想和做的一些工具,文章并不主要介绍和推广我们的工具,我们更多的介绍的是我们业务发展过程中,遇到各种问题的时候,我们的思考和想法,以及我们采用的对策和措施。当然,如果大家有更好的想法,希望大家能贴在讨论区,我们一起讨论一波。 什么是前端工程化 我们在思考这个问题之前,我们先思考一个更大的问题,什么叫软件工程化?我依稀记得,在我还有头发的时候,在梧桐树旁的十五栋楼上,上课(睡觉)的日子里,那满脸褶皱的软件工程老师是这么说的: 将系统化的、严格约束的、可量化的方法应用于软件的开发、运行和维护,即将工程化应用于软件。 没记错的话是应该是清华大学出版社出版的《软件工程》这本书里的定义,回去如果我翻到了是哪本书,我再贴出来。不管,这里先套用一下,前端的工程化是不是应该是指: 将系统化的、严格约束的、可量化的方法应用于前端页面的开发、运行和维护,即将工程化应用于前端开发。 所以,当我们要讨论前端工程化的时候,是不是讨论的这么几个基本问题: 如何进行系统化的前端开发、运行和维护? 如何对前端的开发、运行和维护进行严格约束? 如何对前端的开发、运行和维护进行量化? 为了方便的进行行文,在此约定,下文的开发将指代开发、运行和维护。所以,在后面我们将就这三个问题,来谈谈我们对于前端工程化的思考和实践。 大部分情况下,我们在讨论前端工程化的时候,我们其实只讨论CI/CD和脚手架,但是软件开发并不是只有CI/CD,在不同的开发阶段,甚至在不同的项目阶段,面对不同的问题,我们都要采用不用的方案方法。里面有共性,也有个性。共性的问题,我们可以参考社区的解决方案,但是个性的问题,则需要我们自己去探索和思考。 为什么要进行前端工程化 ok,按照三段论的基本套路,我们应该要来讨论一下,为啥要进行前端的工程化? 如果你非要我吹逼的话,我会这样吹:世界的本质是熵增的,而生命的本质是熵减的。为了体现我们作为生命的伟大价值,我们将坚定不移的将负熵进行到各个角落。 说一个比较通俗的答案:当前的开发环境已经复杂到影响我们开心的进行下一步的开发,我们需要对当前的开发环境进行一次整理,而这个整理的过程,就是前端工程化的过程。 那我们现在的开发环境到底有多复杂? 截止到我写这篇文章的这一刻,我们一共要负责9大系统。各系统由于立项时间,定位,运行环境,产品要求等各种乱七八糟的原因,使用了不同的技术架构,不同的技术框架,不同的开发方式,不同的发布方式。更恐怖的是,由于技术的不断发展,框架的推陈出新,我们的项目还在不断的复杂中。。。 分别为:电竞官网(可以看直播的那种),电竞ipad版本(可以看直播的那种),电竞移动端(android 和 ios两端各70+场景页面,还有一堆的分享页面),电竞移动助手端(android和ios双端),电竞pc助手端(主播管理端),电竞运营管理端(内部数据管理,各种个性化需求和数据分析工具),电竞开放平台(提供给外部合作伙伴进行数据管理),公会管理系统(用于辅助公会进行主播管理),无以计数的活动页面。 所以,每次当有新同学加入我们的时候,我们都必须要提供多位资深员工,手把手的说明一下,我们项目到底要怎么跑起来,有的项目因为实在太复杂,已经快要跑不起来了:D 如何进行前端工程化 面对日益复杂的业务场景,日益庞复的代码逻辑我们开始思考我们到底要如何解决我们的前端工程化问题。根据前面我们的思考,我们将在以下三个方面来谈谈我们对于前端工程化的思考: 如何进行系统化的前端开发、运行和维护? 对于这个问题,我们首先思考的是什么是系统化?我们要怎么做才能称的上是系统化? 抛开那些高大上的术语,我们的解是,将日常开发的各个步骤整化为固定的流程,当我们在日常开发中,面对各种情况都会有固定的一二三四五的时候,就说明我们完成了系统化。 那么,我们每天都要做哪些工作呢? 收集一下大家的日常工作,都有这么一堆: [图片] 这些庞杂的工作有没有规律? 我们开始对我们的工作流程进行了一次收归。其实如果按照需求开发的基本流程,我们可以简单的把各种毛细的工作收归为以下四个阶段: [图片] 在不同的阶段,我们需要做不同的事情,而且,我们发现这些事情是重复的,或者说,是每次做需求的时候在相同的阶段都需要重复的。 在相同阶段里,做重复的事情,我们是不是能够自动化起来? 当然可以,我们开发一个流程管理工具是不是就可以了!在开发之前,我们看了一下公司内和业内在这个方向上的实践。这里列几个公司内宣传的比较好的方案:weflow、feflow、leah-cli 当然社区也有很多很不错的方案比如vue-cli,还有ng-cli等等,完成度相当高,非常的靠谱和好用。 但是,我们不难发现,这些cli也好,flow也好,都是基于工具的思路去实现的,和我们的思路并不一样。我们希望是能基于流程去实现的,而且能贯穿整个开发流程,形成一个闭环。让我们的同学能够系统化的去一二三就好了,不要思考太多有的没的。所以,我们基于上述的开源实现和思路,自己实现了一套新的方案:pgg-flow。 pgg-flow是以开发周期为核心,辅以各种工具库。举个栗子: 在需求的准备阶段,不管三七二十一,我们先来个pgg-flow start,然后开发分支就自动从主干拉出,并将本地环境切入到该分支。 在需求的开发阶段,来个pgg-flow dev,代码就跑起来了,dev-server,本地构建就跑起来了。 在需求的测试阶段,来个pgg-flow test,代码就提交远端,触发服务端ci/cd,测试环境立即生效就绪。 在需求的发布阶段,来个pgg-flow release,直接就触发ci/cd ,发起merge request(用于code review),生成ars单,触发发布系统流程,触发codecc的自动代码审查,并回收占用的各种资源。 我们希望开发同学,能够不用关心不同模块间的框架和依附系统差异。只要知道以上四个命令就能够完成所有的开发流程。 我们在CI/CD的思考和实践详情参考我们电竞小哥思名的这篇文章《weex/vue 应用的服务器端构建之路》,利用内部基于docker的orange-ci和发布系统,实现git工作流到外网发布的一条龙打通。 当然,在这个基础上,我们可以继续去支持,new page, new api等等更具体的业务辅助工具。通过如此,我们磨平了不同框架体系间的差异,大家可以很开心的进行需求开发了。但是,很快大家又有新问题来了,不同的框架中,不同的开发体系在相同的事情上,使用了不同的技术选型。比如,pc的同学自己基于fetch进行了封装,但是在h5上使用了axios来进行请求的。 这个主要是很多历史遗留,开发pc的和开发h5的在很长一段时间内都不是同一拨人,虽然在开发相同或者说相近的业务,但是,技术选型并不一致,这其实是之前遗留的一个很大的坑。 不管问题之前如何产生的,但是,就事论事,如何让大家的技术选型和技术实践相贴近? 如何让大家的技术选型和技术实践相贴近? 对于这个问题,我们的思考是这样的: 大家选型和实践有较大差异的原因是没有现成的方案,或者不知道有现成的方案。所以我们是从三个角度来解决这个问题的: 提供横跨框架场景的公共库,包含完整的基础方法 提供自动化更新的文档,帮助大家更好的了解基础方法有哪些 提供完善的脚手架,减小使用代价 公共库的管理是基于tnpm(公司内部的npm镜像)。 文档自动化,可以参考我们电竞一姐的这篇文章《企鹅电竞前端公共库文档自动化建设》。文章详细的介绍了,我们是如何使用蓝盾来进行文档自动化管理的。 脚手架的提供,为的是进一步减少重复工作量。 当我们完成以上的工作的时候,我们发现,我们已经基本实现了,系统化的进行前端的页面需求开发。当同学们开始一个需求的时候,可以很容易的借助既成的体系,进行快速的开发迭代。当然,现在这个工作模式还比较稚嫩,在很多细节上的地方还需要继续打磨。 为啥要采用相同或者相近的的技术实践?有个很大的原因是为了能够将优化的工作更容易的扩散出去。 比如,通过webpck4+babel7 的升级,一下就减少了移动端几乎所有页面的40%体积,具体可以参考思名的《weex/vue 应用的webpack4 升级优化之路》,《真·一行代码优化30%的JS bundle体积》。我们觉得优化应该倾向于底层,脱离于业务,只有如此,才能实现更高层级的复用。 如何对前端的开发、运行和维护进行严格约束? 在实现了系统化的进行开发和迭代之后,我们不得不开始面对开发质量的问题。如何对开发质量进行有效的约束和评估,这并不是一个简单的问题。我们最早的方案使用了业内比较通用的方案: 使用本地的eslint进行代码管控 通过eslint + husky + lint-staged来搭建一套基于本地的代码及风格管控,这个一个很棒的方式。业内对于这个方案也有很多很好的实践,大家自行百度一下就好。但是,这个方案对于我们而言,并不能解决所有的问题。 我们希望能对代码质量有一个整体的认识,我们希望能够对于代码质量的监控有一个比较好的客观衡量。 我们希望这个监控体系能够不阻塞大家正常的业务交付。 我们希望这个体系能够从各个维度对我们的代码进行合理的分析。 很明显,本地化的eslint根本不能满足我们对于代码质量监控的要求,就算,你在这个基础上增加prettier来做你的代码格式化的管理工具。于是我们开始寻找,是否有能够有一个平台来帮助我们完成这个工作。 如何优化的监控的前端代码质量 这个问题,我们最后使用了codecc+蓝盾的解决方案。将质量监控体系建设在远端,能够有效的对代码进行监控,同时,也能很容易的让更多的同学0成本的使用起来。有兴趣的话,大家可以参考我们电竞一姐的《如何优雅的监控前端代码质量》。 codecc是公司内部的在线代码自动审查平台,可以实现在线的代码eslint,coverity,重复性,复杂度等各种纬度的代码审查,能够将审查结果进行很好的分析和数据图形化。蓝盾是公司内部的流程集成系统,可以流水线化的实现代码的自动化构建和二三级子服务的连接。 通过蓝盾和codecc的体系,我们发现并解决了很多的问题,但是,很快,我们又发现了,单纯的依靠系统自动化的检查和检测,并不能解决所有的问题。有些设计思路上的问题,我们没办法简单的通过自动化的代码审查来解决。那么是否可以在必要的时候,引入人工的代码审查来进一步的提高代码质量。 在合适的时候进行code review 不能否认,cr是一个很重要,但是很烦人的工作。如何让大家对代码在合适的时候,进行cr,这个问题让我们思考了很久。我们尝试过很多的方案: 在发布流程中通过人工要求增加cr环境 以代码小课堂的形式,进行cr 在开发前后以技术评审的形式来进行cr 但是,这些方式有好有坏,但是,都不能够像水一样的融入到我们的开发生活中来。我们觉得不自然。我们最后的思考是: 将code review和git 工作流和ci相互融合,通过ci工具来自动触发。利用cli来封装各种常用操作,将原本复杂的命令操作,化繁为简。 简单的介绍一下我们的方案: [图片] 为了能够使这一套工作流变的更智能,我们利用node-git构建了一个轻便的git工具。具体的实现方式可以参考我们电竞小哥锦亮的《基于NodeGit的Git工作流封装》。 我们之前讨论了很久的代码质量的管控,但是除了代码质量,还有一个很大的部分是我们的行为。我们在开发过程中,有很多的行为会改变远端的代码状态和共用资源(测试服务器)的状态。我们怎么才能对这个状态进行一个很好的管理? 如何监控代码的过程? 在电竞小哥的《weex/vue 应用的服务器端构建之路》一文中," 通过工蜂的webhook监控提交" 这段内容详细的介绍了我们的实践。除了这部分介绍的实践,我们还搭建了一个node sever用来记录和查询,我们的测试服务器的使用情况。 简单的说,我们实现了各大系统的过程内容收归,各个不同平台的流程数据能够统一的以企业微信机器人的形式主动推送给你。足不出户,可知天下事。 通过完善的自动化代码监控 + 人工cr的双检查,我们简单的构建起了我们的代码质量监控体系。 对于前端而言,轻薄短小快是基本要求,但是做到轻薄短小快并不是一件简单的事情,更复杂的事情,我们有这么多的工作要做,居然还有一群人在后面用排期表抽我们的屁股,这个就让我们很难受了。 如何对前端的开发、运行和维护进行量化 对工作进行量化是一件很复杂,很难把控的事情。我们在这方面并没有很好的实践。对于如何正确的评估工作量, 我们有一个不怎么成熟的想法: 所有工作量应该包含以下几个部分: 技术方案预研所花费的时间 技术方案落地所耗费的时间 自测的时间 联调的时间 产品修改需求的时间 调试解决问题的时间 在具体的时间分配上,根据各个不同的人自我确定和分配。在具体的实践过程中,我们建议保留一定的buffer,你总会在你的代码上线前,发现这样或那样的问题的。 除了上面的建议,我们还有一些比较通用的建议: 比如实现从设计到结果交付的组件化管理,组件化一定要从设计语言开始,这样才能从根本上减少各个流程的耗时,和实现标准化。 技术预研要早于产品开发。预研一代,落地一代,维护一代。永远不要在业务实际开发中使用未经预研的框架和技术体系,会引入极大的项目风险。 使用graphql这种前端为核心的接口配置体系,能够极大的减少因为接口联调带来的不稳定耗时。 以上,简单的介绍了一下,我们现在的前端工程化的体系进程,和我们的一些思考。很多观点还比较的幼稚,很多实现还特别的落后,希望各位大佬,能够给我们提供更多好的思路和方式方法,大家一起讨论和思考如何更优雅的进行完善的前端工程化。 最后打一波广告: 企鹅电竞——腾讯游戏官方直播平台,欢迎UI开发和前端开发同学加入 https://hr.tencent.com/position_detail.php?id=48298
2019-03-11 - canvas画图随记
最近画了一张分享图,在此记录一下遇到的问题及解决方法。 画布尺寸自适应 微信小程序尺寸为rpx,会自适应各种机型,但canvas的方法参数默认为px,所以需要对画布上的每一项参数乘以(画布宽度/设备屏幕宽度),将rpx换算成px,达到尺寸自适应的目的,所以将此系数设置为全局变量。代码如下: [代码]var app = getApp(); const device = wx.getSystemInfoSync(); const width = device.windowWidth;//设备屏幕宽度 const xs = width / 375; [代码] 调用: [代码]createCard: function() { var context = wx.createCanvasContext('myCanvas'); context.fillText('内容', 100 * xs , 100 * xs) } [代码] 长文本换行 由于fillText只能画一行,但很多情况下是需要将长文本自动换行展示的,这个时候则需要对文本进行处理。 方法:遍历该文本,计算出每一字宽度之和,当该宽度大于文本最大宽度时绘制当前截取部分,并将绘制高度加上行高,宽度置0,重新计算并绘制下一行。当只剩最后一字时,绘制剩余部分。 缺陷:当文本内有换行符时,绘制会换行,但当前计算宽度不会增加,导致格式混乱。所以需要在计算宽度之和前判断该字符是否为换行符,若果是,则绘制当前部分,开始下一行的计算。 完善:如果需要知道绘制文本的总高度,设置初始文本高度为0,在绘制一行时加上行高则可。代码如下: [代码] /** * context:当前画布对象 * text:文本内容 * leftWidth:文本左上角x坐标 * initHeight:文本左上角y坐标 * canvasWidth:一行文本最大宽度 */ drawText: function(context, text, leftWidth, initHeight, canvasWidth) { var lineWidth = 0; //文本宽度 var textHeight = 0; //文本总高度 var lastSubStrIndex = 0; //每次开始截取的字符串的索引 for (let i = 0; i < text.length; i++) { if (text[i] == "\n") { //如遇换行 context.fillText(text.substring(lastSubStrIndex, i), leftWidth, initHeight, canvasWidth); //绘制截取部分 initHeight += 17.5 * xs; //17.5为字体高度 lineWidth = 0; lastSubStrIndex = i + 1; //截取字符串时跳过换行符 textHeight += 17.5 * xs; } else { lineWidth += context.measureText(text[i]).width; //计算每个字的宽度之和 if (lineWidth > canvasWidth) { context.fillText(text.substring(lastSubStrIndex, i), leftWidth, initHeight, canvasWidth); initHeight += 17.5 * xs; lineWidth = 0; lastSubStrIndex = i; textHeight += 17.5 * xs; } } if (i == text.length - 1) { //绘制剩余部分 context.fillText(text.substring(lastSubStrIndex, i + 1), leftWidth, initHeight, canvasWidth); textHeight += 17.5 * xs; } } return textHeight; }, [代码] 调用: [代码] var text = '新建项目选择小程序项目,选择代码存放的硬盘路径,填入刚刚申请到的小程序的 AppID,给你的项目起一个好听的名字,最后,勾选 "创建 QuickStart 项目" (注意: 你要选择一个空的目录才会有这个选项),点击确定,你就得到了你的第一个小程序了,点击顶部菜单编译就可以在微信开发者工具中预览你的第一个小程序。'; context.setFontSize(15 * xs) that.drawText(context, text, 30 * xs, 100 * xs, 320 * xs) [代码] 高度自适应 如碰到画布高度需要根据内容高度不同而不同,或者某元素与可变化高度的元素固定距离的情况,则需要计算出可变化元素高度,再根据该高度进行计算其他高度。例如: [图片] 微信图标始终距离文本30px,而该文本高度可变,所以图标的左上角y轴坐标=文本y轴坐标+文本高度+下边距,代码如下: [代码]var textHeight = that.drawText(context, text, 30 * xs 100 * xs, 320 * xs) context.drawImage('/images/wx.png', 68 * xs, (100 + 30) * xs + textHeight, 80 * xs, 80 * xs) [代码] 注意:因为计算文本高度的方法里已经乘过系数,所以这里不需要乘。宽度自适应同理。 绘制圆角矩形框 由于没有绘制圆角矩形的方法,所以需要将圆角矩形分开绘制。 方法:将四个圆角当成四分之一圆绘制,然后分别画四条边,坐标如下图所示。 [图片] 代码: [代码] /** * context:当前画布对象 * x:圆角矩形左上角x坐标 * y:圆角矩形左上角y坐标 * w:宽度 * h:高度 * r:border-radius * color:填充颜色 */ roundRect(ctx, x, y, w, h, r, color) { ctx.beginPath() // 左上角 ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5) // 上边框 ctx.moveTo(x + r, y) ctx.lineTo(x + w - r, y) ctx.lineTo(x + w, y + r) // 右上角 ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2) // 右边框 ctx.lineTo(x + w, y + h - r) ctx.lineTo(x + w - r, y + h) // 右下角 ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5) // 下边框 ctx.lineTo(x + r, y + h) ctx.lineTo(x, y + h - r) // 左下角 ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI) // 左边框 ctx.lineTo(x, y + r) ctx.lineTo(x + r, y) //填充颜色 ctx.setFillStyle(color); ctx.fill() ctx.closePath() } [代码] 调用: [代码]that.roundRect(context, 15 * xs, 60 * xs, 350* xs, 200 * xs, 14 * xs, '#ffffff') [代码] 文本加粗 官方文档里有说到font的使用规则与css语法一致,有几个需要注意的地方,否则可能会导致设置无效。 [图片] 调用: [代码] context.font = "normal bold 27px sans-serif"; context.setFontSize(27 * xs) context.fillText('加粗字体', 100 * xs , 145 * xs) [代码] 效果: [图片] 注意:在真机上若没有写第一个normal参数,则不能成功设置。 字体大小可以在下面重新赋值。 如果没有效果可以注意console有没有如下图所示 设置无效的警告,原因很大可能是因为参数写的不对。 [图片] 圆形头像绘制 方法:在画布上剪切一个圆,然后在圆上画头像,最后恢复即可。有一个需要注意的地方,drawImage方法只能绘制本地图片,如果需要绘制网络图片需下载完成之后再画。代码如下: [代码] context.save() context.beginPath() context.arc(77 / 2 * xs + 150 * xs, 77 / 2 * xs + 73 * xs, 77 / 2 * xs, 0, Math.PI * 2, false) context.clip() var headimg = '/images/headimg.jpg' context.drawImage(headimg, 150 * xs, 73 * xs, 77 * xs, 77 * xs) context.restore() context.draw(); [代码] 遇到的问题:当图片为长方形时,强行将图片压缩为正方形会导致头像变形。 解决办法:image组件里,参数mode有一个值为aspectFill,即保持纵横比缩放图片,只保证图片的短边能完全显示出来,我们参考这种思路来截取图片。 [图片] 这里以宽比高长的图为例。如上图所示,圆为头像显示位置,线为中线,矩形框为一张宽大于高的图片。矩形左上角即为画图时的左上角坐标。截部分如图所示,得到图片宽高后,短边固定为头像尺寸,长边根据短边缩放比计算得到。图片宽=原图宽 /(头像高 / 原图高)。左上角的x轴坐标为:中线x坐标 - 图片宽 / 2。代码如下所示: [代码] context.save() context.beginPath() context.arc(77 / 2 * xs + 150 * xs, 77 / 2 * xs + 73 * xs, 77 / 2 * xs, 0, Math.PI * 2, false) context.clip() var headimg = '/images/headimg.jpg'; //头像路径 var headimgHeight = 0; var headimgWidth = 0; wx.getImageInfo({ src: headimg, success(res) { headimgHeight = res.height; //原图高度 headimgWidth = res.width; //原图宽度 //当宽 > 高时 if (headimgWidth > headimgHeight) { var width = headimgWidth / (headimgHeight / (77 * xs)); //图片宽度 var x = (150 + 77 / 2) * xs - width / 2; //x轴坐标 context.drawImage(headimg, x, 73 * xs, width, 77 * xs) } else { //当高>=宽时 var height = headimgHeight / (headimgWidth / (77 * xs)); //图片高度 var h = (73 + 77 / 2) * xs - height / 2; //y轴坐标 context.drawImage(headimg, 150 * xs, h, 77 * xs, height) context.restore() context.draw(); } } }) [代码] 注意:这里得到的图片宽高已经是px为单位,所以不乘系数。
2019-03-18