2天实现一个带壳截图小程序

1、什么是带壳截图

我们在日常工作和生活中,往往需要截图去做汇报或分享。

普通人的做法:手机或电脑截图,然后就拿去汇报或分享。这样你的截图显得平平无奇,没有给人眼前一亮的感觉。

但是,如果我们给这张截图穿上一件“衣服”,把它打扮一下,让她鹤立鸡群,与众不同,那么就会给人留下深刻的印象!!

无设备边框 带设备边框
无边框
带边框

2、为什么要自己实现一个带壳截图

目前市面上有很多带壳截图小程序,但是他们有很多无法满足我要求的一些痛点;

痛点1:移动端带壳截图的App,高级功能需要收费;

痛点2:免费的带壳截图小程序,要么设备机型不全,要么保存的截图不高清;

痛点3:设备机型齐全的网站,设置过于复杂,而且需要电脑访问,操作不便捷;

所以,作为程序员,我想自己实现一个免费、设备机型齐全、在手机端可随时访问截图的小程序;

3、如何实现

带壳截图有两个要素,一个是设备边框,一个是上传的截图。

3.1 设备边框素材

对于苹果系列产品,其产品边框资源,可在网上找到,该资源必须为png格式的,例如:

MacBook_Air_5th_13_M1_Gold.png

得到产品边框资源后,我们需要通过PS得到其宽(width)、高(height)参数,显示器的左上角坐标信息(startX, startY)和右下角坐标信息(endX, endY),通过官网可得知每一种设备的颜色信息;

将这些信息抽象化后,得到每一个机型设备的具体信息:

机型信息:

const macs = [
  {
    "id": "MackBook_Air",
    "name": "第五代 Air",
    "products": [
      {
        "id": "MacBook_Air_5th_13_M1",
        "name":"13寸 M1",
        "colorsId": "Air_5th_13_M1",
        "w": 3310,
        "h": 1897,
        "sx": 375,
        "sy": 123,
        "ex": 2935,
        "ey": 1723
      },
      {
        "id": "MacBook_Air_5th_13_M2",
        "name":"13寸 M2",
        "colorsId": "Air_5th_13_M2",
        "w": 3220,
        "h": 2100,
        "sx": 329,
        "sy": 218,
        "ex": 2889,
        "ey": 1882
      }
    ]
  }
];

颜色信息:

const colors = {
  "Air_5th_13_M1": [
    {
      "id": "Gold",
      "value": "#f4e8ce"
    },
    {
      "id": "Silver",
      "value": "#edeee7"
    },
    {
      "id": "Space_Grey",
      "value": "#424242"
    },
  ],
  "Air_5th_13_M2": [
    {
      "id": "Midnight",
      "value": "#31353b"
    }
  ]
};

3.2 小程序实现

将页面分为两个区域,上面为截图预览展示区,下面为操作区,具体如下所示:


带壳截图

其中,截图预览展示区采用canvas承载,操作区采用普通wxml承载;

截图预览区之所以采用canvas承载,原因是canvas展示图片不会出现抖动的效果,而使用image标签,在加载、展示图片的过程中会出现抖动的效果,影响体验;

使用canvas绘制需要注意:为了绘制出高清的图像,必须得对canvas的尺寸进行相应的缩放:

const query = wx.createSelectorQuery();
query.select('#myCanvas')
  .fields({ node: true, size: true })
  .exec((res) => {
    const canvas = res[0].node;
    const ctx = canvas.getContext('2d');
    const dpr = wx.getSystemInfoSync().pixelRatio;
    // 重点部分
    canvas.width = res[0].width * dpr;
    canvas.height = res[0].height * dpr;
    ctx.scale(dpr, dpr);
  })
}

3.3 截图预览绘制

按照图层的顺序,先后绘制Logo、文本、产品 边框图;

3.3.1 Logo 绘制

根据每个设备屏幕的左上角(sx,sy)、右下角(ex,ey)的坐标信息,以及Logo的宽高信息,可以得到绘制Logo的(x, y, width, height)参数,然后进行绘制:

// Logo 绘制
ctx.drawImage(img, x, y, width, height);

3.3.2 文案绘制

根据每个设备屏幕的左上角(sx,sy)、右下角(ex,ey)的坐标信息,以及素材的素材的缩放比例,可以得到绘制文案的参数,然后进行绘制:

const { sx, sy, ex, ey } = currentProduct;
const xPixel = ex - sx;
const yPixel = ey - sy;

const x = sx * scale;
const y = sy * scale;

const width = xPixel * scale;
const height = yPixel * scale;

ctx.fillText(`设备分辨率: ${xPixel} x ${yPixel} px`, x + width / 2, y + height - 10);

3.3.3 上传截图绘制

根据每个设备屏幕的左上角(sx,sy)、右下角(ex,ey)的坐标信息,以及素材的素材的缩放比例,可以得到绘制截图的参数,然后进行绘制:

const { sx, sy, ex, ey } = currentProduct;

const x = sx * scale;
const y = sy * scale;

const width = (ex - sx) * scale;
const height = (ey - sy) * scale;

ctx.drawImage(img, x, y, width, height);

3.3.4 产品边框绘制

对于,任意的产品边框素材,有的素材高度 > 宽度,有的素材宽度 > 高度;

为了让素材边框很好适配展示区域的尺寸,需要根据上面两种情况进行区分,然后分别设置;

当素材高度 > 宽度时,设置素材的高度和canvas的高度相等,宽度进行同比例缩放,裁剪方式与 object-fit: contian 一致;

const { w, h } = currentProduct;
const imgScale = canvasHeight / h;
const deviceHeight = canvasHeight;
const deviceWidth = imgScale * w;
const startX = (canvasWidth - deviceWidth) / 2;
const startY = (canvasHeight - deviceHeight);
context.drawImage(image, startX, startY, deviceWidth, deviceHeight);

当素材宽度 > 高度时,设置素材的宽度和canvas的宽度相等,高度进行 比例缩放,裁剪方式与 object-fit: contian 一致;

const { w, h } = currentProduct;
const imgScale = canvasWidth / w;
const deviceWidth = canvasWidth;
const deviceHeight = imgScale * h;
const startX = (canvasWidth - deviceWidth) / 2;
const startY = (canvasHeight - deviceHeight) / 2;
context.drawImage(image, startX, startY, deviceWidth, deviceHeight);

3.4 组件化封装

将展示区与操作区封装成一个组件,组件所需的数据从外部传入,iPhone、iPad、MacBook、iWatch数据格式保持一致;

组件化与产品边框素材的抽象数据结合,即可实现一次开发,多处复用;之后不管是需要新增设备类型非常方便,还是同一类型下新增新的产品,只要变更数据即可现实简单高效;

4个Tab页引入该组件,然后给组件传入对应的设备数据和颜色数据即可渲染出展示区与操作区:

<device-display-page
  devices="{{devices}}"
  colors="{{colors}}"
></device-display-page>
iPhone iPad MacBook iWatch
iPhone系列
iPad系列
MacBook系列
iWatch系列

3.5 离线 canvas 保存截图

在开始时,虽然对canvas进行了宽高设置以及缩放设置,但是小程序中对素材缩小尺寸后,再在使用canvas绘制绘,会导致图像失真,看起来有模糊的情况。

为了得到高清截图,我们采用离线canvas绘制素材,canvas的宽高尺寸与边框产品的宽高尺寸保持一致,不进行缩放处理,然后将canvas转换为图片,再保存到相册中;

const { w, h } = currentProduct;
// 创建离屏 2D canvas 实例
const canvas = wx.createOffscreenCanvas({type: '2d', width: w, height: h });
// 获取 context。注意这里必须要与创建时的 type 一致
const context = canvas.getContext('2d');

// 绘制部分代码省略,可参考3.3.3和3.3.4

// 获取画完后的数据
const base64 = context.canvas.toDataURL('image/png');
const formatedBase64 = base64.replace('data:image/png;base64,', '')
      .replace(/[\r\n]/g, '')
const shotScreenPath = `${wx.env.USER_DATA_PATH}/shotScreen.png`;
fs.writeFile({
    filePath: shotScreenPath,
    data: formatedBase64,
    encoding: 'base64',
    success: (res) => {
      wx.saveImageToPhotosAlbum({
        filePath: shotScreenPath,
        success(res) { 
          wx.showToast({
            title: '边框保存成功',
          })
        },
        complete() {
          wx.hideLoading()
        }
      });
    },
    fail: (err) => {
      console.log('err:', err);
    }
})

4、小程序部署

相对于H5,小程序部署非常简单,只要将代码上传,然后提审就可以,提审的前提是你已经注册了小程序号。

5、总结

  • 在网上找到产品边框素材资源;
  • 获得素材资源的尺寸与宽高信息,并抽象化;
  • 页面区域划分并抽象组件化,便于不同Tab页面复用;
  • 展示区域采用常规canvas,将素材进行尺寸缩放,便于展示;
  • 保存截图采用离线canvas,不进行缩放,canvas尺寸等于产品边框素材尺寸,提高截图分辨率;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容