npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ste-canvas-poster

v1.0.2

Published

基于 Canvas 2D API 的声明式海报绘制引擎,支持微信小程序与 APP 双端

Readme

ste-canvas-poster

基于 Canvas 2D API 的声明式海报绘制引擎,支持微信小程序与 APP 双端。

特性

  • Schema 驱动 — 通过 JSON 描述海报结构,数值直接使用设计稿 rpx
  • 双端一致 — 一套 Schema 同时适配微信小程序与 APP,无需条件编译
  • 模板变量 — Schema 中 {{key}} 自动替换为 data 数据
  • Flex 布局 — view 容器支持 display: 'flex' 及子元素 margin
  • 内置二维码 — qrcode 类型直接生成二维码,无需额外依赖
  • TypeScript 支持 — 完整类型声明,开发时自动补全

安装

npm install ste-canvas-poster

快速开始

1. 定义 Schema

const schema = {
  width: 750,
  height: 1068,
  background: "#FFFFFF",
  views: [
    {
      type: "view",
      css: {
        left: 54,
        top: 54,
        width: 642,
        height: 960,
        borderRadius: 20,
        background: "#FFFFFF",
      },
    },
    {
      type: "image",
      src: "{{coverImage}}",
      css: {
        left: 96,
        top: 160,
        width: 560,
        height: 478,
        borderRadius: 12,
        objectFit: "cover",
      },
    },
    {
      type: "text",
      text: "{{titleText}}",
      css: {
        left: 96,
        top: 730,
        fontSize: 32,
        fontWeight: "bold",
        color: "#181818",
        maxWidth: 560,
        ellipsis: true,
      },
    },
    {
      type: "qrcode",
      src: "{{qrcodeUrl}}",
      css: { left: 504, top: 842, width: 160, height: 160 },
    },
  ],
};

2. 准备数据

const data = {
  coverImage: "https://example.com/cover.jpg",
  titleText: "精选商品",
  qrcodeUrl: "https://example.com",
};

3. 渲染与保存

import { renderPoster } from "ste-canvas-poster";

// 渲染
const engine = await renderPoster({
  schema,
  data,
  selector: "#myCanvas",
  vm: this,
});

// 保存到相册
await engine.saveToAlbum();

// 或获取临时路径(用于分享)
const tempPath = await engine.toTempFilePath();

4. Template 模板

<template>
  <!-- #ifdef MP-WEIXIN -->
  <canvas id="myCanvas" type="2d" class="canvas" :style="canvasStyle" />
  <!-- #endif -->
  <!-- #ifdef APP-PLUS -->
  <canvas
    canvas-id="myCanvas"
    id="myCanvas"
    class="canvas"
    :style="canvasStyle"
  />
  <!-- #endif -->
</template>
computed: {
  canvasStyle() {
    return { width: '750rpx', height: '1068rpx' };
  }
}

API

renderPoster(options)

渲染海报,自动处理rpx 转换和平台差异。

function renderPoster(options: RenderPosterOptions): Promise<PosterEngine>;

参数

| 参数 | 类型 | 必填 | 默认值 | 说明 | | ---------- | --------------------- | ---- | -------- | ------------------------------------- | | schema | PosterSchema | 是 | - | 海报结构描述 | | data | TemplateData | 否 | {} | 模板变量数据 | | selector | string | 是 | - | Canvas 选择器,如 '#myCanvas' | | vm | Record<string, any> | 是 | - | Vue 组件实例 | | dpr | number | 否 | 自动获取 | 像素比 | | useRpx | boolean | 否 | true | 是否将 schema 数值视为 rpx 并自动转换 |

返回值Promise<PosterEngine> — 引擎实例

PosterEngine

渲染完成后返回的引擎实例,提供以下方法:

engine.saveToAlbum()

导出图片并保存到系统相册。

saveToAlbum(): Promise<string>

返回值:临时文件路径

engine.toTempFilePath(options?)

导出为临时文件路径,不保存到相册。

toTempFilePath(options?: ToTempFilePathOptions): Promise<string>

| 参数 | 类型 | 默认值 | 说明 | | ---------- | ---------------- | ------- | ---------------------------- | | fileType | 'png' \| 'jpg' | 'png' | 导出格式 | | quality | number | 1 | 图片质量(0-1),仅 jpg 有效 |

返回值:临时文件路径

engine.destroy()

销毁引擎实例,释放图片缓存等资源。组件卸载时调用。

destroy(): void

工具函数

rpx2px(rpx)

将 rpx 转换为 px(基于当前屏幕宽度)。

function rpx2px(rpx: number): number;

px2rpx(px)

将 px 转换为 rpx。

function px2rpx(px: number): number;

getCanvasNode(selector, vm)

获取 Canvas 节点(双端统一封装)。

function getCanvasNode(selector: string, vm: Record<string, any>): Promise<any>;

measureText(text, fontSize, bold?)

计算文本渲染宽度,考虑中英文、数字、符号的宽度差异。

function measureText(text: string, fontSize: number, bold?: boolean): number;

| 参数 | 类型 | 必填 | 默认值 | 说明 | | ---------- | --------- | ---- | ------- | ------------------- | | text | string | 是 | - | 文本内容 | | fontSize | number | 是 | - | 字号(rpx) | | bold | boolean | 否 | false | 是否加粗(加宽 6%) |

返回值:文本宽度(rpx),向上取整


Schema 规范

根节点

{
  width: 750,                  // 画布宽度(rpx)
  height: 1068,                // 画布高度(rpx)
  borderRadius: 24,            // 画布圆角(可选,导出圆角图片)
  background: '#FFFFFF',       // 背景色或渐变(可选,不设置则透明)
  backgroundImage: '{{bg}}',   // 背景图(可选,支持模板变量,加载失败回退到 background)
  views: []                    // 元素列表,按顺序绘制
}

背景配置

| 配置 | 效果 | | ----------------------------------------------------------------- | ------------------------- | | 不设置 backgroundbackgroundImage | 透明,导出 PNG 带透明通道 | | background: '#FFFFFF' | 白色背景 | | background: 'linear-gradient(180deg, #FF0000 0%, #FFFFFF 100%)' | 渐变背景 | | backgroundImage: '{{bg}}' | 背景图 |

圆角说明:设置 borderRadius 后,整个画布被裁剪为圆角矩形,圆角外区域透明。

元素类型

view — 容器

支持背景、边框、圆角和 Flex 布局,可嵌套子元素。

{
  type: 'view',
  css: {
    left: 54,
    top: 54,
    width: 642,
    height: 960,
    borderRadius: 20,
    background: '#FFFFFF',
    borderWidth: 2,
    borderColor: '#CCCCCC'
  },
  views: []  // 子元素
}

渐变背景

background: "linear-gradient(180deg, #EE3C3C 0%, #FFFFFF 100%)";
// 角度遵循 CSS 规范:0deg 从下到上,90deg 从左到右,180deg 从上到下

Flex 布局

{
  type: 'view',
  css: {
    left: 54, top: 54, width: 642, height: 200,
    background: '#F5F5F5',
    borderRadius: 12,
    display: 'flex',
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    padding: [20, 30]  // [上下, 左右] 或 [上, 右, 下, 左]
  },
  views: [
    { type: 'image', css: { width: 30, height: 30, borderRadius: 15, marginRight: 8 } },
    { type: 'text', text: '{{name}}', css: { fontSize: 24, width: 100, height: 24 } }
  ]
}

image — 图片

绘制图片,支持圆角裁剪和 objectFit。

{
  type: 'image',
  src: '{{coverImage}}',  // 支持模板变量
  css: {
    left: 96,
    top: 160,
    width: 560,
    height: 478,
    borderRadius: 12,
    objectFit: 'cover'     // 'fill' | 'cover' | 'contain'
  }
}

text — 文本

绘制文本,支持多行换行、省略号、删除线。

{
  type: 'text',
  text: '{{titleText}}',  // 支持模板变量
  css: {
    left: 96,
    top: 730,
    fontSize: 32,
    fontWeight: 'bold',     // 'normal' | 'bold' | '400' | '700'
    fontFamily: 'sans-serif',
    color: '#181818',
    textAlign: 'left',      // 'left' | 'center' | 'right'
    lineHeight: 1.4,        // 行高倍数(不是 px)
    maxWidth: 560,          // 超过则换行或省略
    ellipsis: true,         // 单行省略号
    lines: 2                // 最大行数(0 = 不限)
  }
}

qrcode — 二维码

生成并绘制二维码,内容支持模板变量。

{
  type: 'qrcode',
  text: '{{qrcodeUrl}}',   // 或 src: '{{qrcodeUrl}}'
  css: {
    left: 504,
    top: 842,
    width: 160,
    height: 160,
    color: '#000000',                       // 前景色
    background: '#FFFFFF'                   // 背景色
  }
}

CSS 属性参考

通用属性

所有元素均可使用:

| 属性 | 类型 | 默认值 | 说明 | | -------------- | -------------------- | ------ | ------------------------------ | | left | number | 0 | x 坐标 | | top | number | 0 | y 坐标 | | right | number | - | 右侧定位 | | bottom | number | - | 底部定位 | | width | number | - | 宽度 | | height | number | - | 高度 | | opacity | number | 1 | 透明度(0-1),不参与 rpx 缩放 | | borderRadius | number \| number[] | 0 | 圆角,支持 [lt, rt, rb, lb] |

view 属性

| 属性 | 类型 | 默认值 | 说明 | | ----------------- | --------------------------------------------- | -------------- | -------------- | | background | string | - | 背景色或渐变 | | backgroundColor | string | - | 同 background | | borderWidth | number | - | 边框宽度 | | borderColor | string | - | 边框颜色 | | display | 'flex' | - | 启用 Flex 布局 | | flexDirection | 'row' \| 'column' | 'row' | 主轴方向 | | alignItems | 'flex-start' \| 'center' \| 'flex-end' | 'flex-start' | 交叉轴对齐 | | justifyContent | 'flex-start' \| 'center' \| 'space-between' | 'flex-start' | 主轴对齐 | | padding | number \| number[] | 0 | 内边距 |

image 属性

| 属性 | 类型 | 默认值 | 说明 | | ----------- | -------------------------------- | -------- | -------- | | objectFit | 'fill' \| 'cover' \| 'contain' | 'fill' | 缩放模式 |

text 属性

| 属性 | 类型 | 默认值 | 说明 | | ---------------- | ------------------------------- | -------------- | ------------------------- | | fontSize | number | 14 | 字号 | | fontWeight | string \| number | 'normal' | 字重 | | fontFamily | string | 'sans-serif' | 字体 | | color | string | '#000000' | 文本颜色 | | textAlign | 'left' \| 'center' \| 'right' | 'left' | 对齐方式 | | lineHeight | number | 1.4 | 行高倍数,不参与 rpx 缩放 | | maxWidth | number | - | 最大宽度 | | ellipsis | boolean | false | 单行省略号 | | lines | number | 0 | 最大行数,不参与 rpx 缩放 | | textDecoration | 'line-through' \| 'none' | - | 文本装饰 |

Flex 子元素 margin

Flex 布局中子元素支持的间距属性:

| 属性 | 类型 | 默认值 | 说明 | | -------------- | -------- | ------ | ------ | | marginLeft | number | 0 | 左间距 | | marginRight | number | 0 | 右间距 | | marginTop | number | 0 | 上间距 | | marginBottom | number | 0 | 下间距 |

不参与 rpx 缩放的字段

| 字段 | 语义 | | ------------ | -------------------------- | | opacity | 透明度 0~1 | | lines | 行数 | | flex | flex 比例 | | lineHeight | 行高倍数 | | fontWeight | 字重(数字形式如 400/700) | | zIndex | 层叠顺序 | | dpr | 像素比 |


命名规范

所有 Schema 及 CSS 属性统一使用 camelCase(驼峰命名):

// ✅ 正确
{ css: { fontSize: 32, borderRadius: 12, objectFit: 'cover' } }

// ❌ 错误
{ css: { 'font-size': 32, 'border-radius': 12, 'object-fit': 'cover' } }

平台差异

| 特性 | 微信小程序 | APP | | ------------- | ----------------------- | -------------------------- | | Canvas 初始化 | canvas.createImage() | uni.createCanvasContext | | 图片路径 | 直接使用网络 URL | 需先 downloadFile 到本地 | | rpx 缩放 | CSS 自动处理,scale = 1 | scale = windowWidth / 750 | | 图片加载 | canvas.createImage() | uni.getImageInfo |


常见问题

海报只显示一部分

Canvas 内部尺寸小于元素坐标范围。确保 canvas.width/height 不小于最大元素坐标,小程序端保持设计稿尺寸。

文字大小与预期不符

fontSize 单位为 rpx,确保 useRpx: true(默认值)。


文件结构

ste-canvas-poster/
├── index.js               # 导出入口
├── index.d.ts             # 函数类型声明
├── types.d.ts             # Schema / CSS 类型声明
├── src/
│   ├── posterAdapter.js   # 双端适配:Canvas 初始化、rpx 转换、图片预下载
│   ├── posterEngine.js    # 绘制引擎:Schema 解析、Canvas 绘制
│   ├── qrcodeGenerator.js # 二维码生成
│   └── measureText.js     # 文本宽度计算
└── package.json

License

ISC