{"id":25763881,"url":"https://github.com/bernankez/backmoji","last_synced_at":"2025-07-28T20:09:03.786Z","repository":{"id":231012893,"uuid":"774677336","full_name":"Bernankez/Backmoji","owner":"Bernankez","description":"A library for creating patterns quickly and easily","archived":false,"fork":false,"pushed_at":"2024-04-23T07:33:40.000Z","size":651,"stargazers_count":0,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-04T04:27:52.706Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://backmoji.keke.cc","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Bernankez.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-20T01:07:47.000Z","updated_at":"2024-04-23T07:33:43.000Z","dependencies_parsed_at":"2024-04-02T03:46:07.487Z","dependency_job_id":"999d8629-f33d-40b8-a5c0-9853243e0a01","html_url":"https://github.com/Bernankez/Backmoji","commit_stats":null,"previous_names":["bernankez/backmoji"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/Bernankez/Backmoji","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bernankez%2FBackmoji","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bernankez%2FBackmoji/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bernankez%2FBackmoji/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bernankez%2FBackmoji/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Bernankez","download_url":"https://codeload.github.com/Bernankez/Backmoji/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bernankez%2FBackmoji/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267578003,"owners_count":24110351,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-28T02:00:09.689Z","response_time":68,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-02-26T20:19:31.846Z","updated_at":"2025-07-28T20:09:03.770Z","avatar_url":"https://github.com/Bernankez.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Backmoji\n\n\u003e [!WARNING]\n\u003e This library is still under development, and the APIs are not stable yet.\n\n## Install\n\n```sh\nnpm install backmoji\n```\n\n## Usage\n\n### With vanilla JavaScript\n\n```js\nimport { backmoji, createImageRenderer } from \"backmoji\";\nimport JSLogo from \"/javascript.png\";\n\nconst canvas = document.createElement(\"canvas\");\ndocument.body.appendChild(canvas);\n\nasync function loadImage(url) {\n  const image = new Image();\n  image.src = url;\n  return new Promise()((resolve) =\u003e {\n    image.onload = () =\u003e resolve(image);\n  });\n}\n\nasync function createRenderer() {\n  return createImageRenderer(await loadImage(JSLogo));\n}\n\nconst { render } = backmoji(canvas, createRenderer(), {\n  rowGap: 15,\n  columnGap: 20,\n  degree: 30,\n});\nrender();\n```\n\n### With Vue\n\n```vue\n\u003cscript setup lang=\"ts\"\u003e\nimport { ref, watchEffect } from \"vue\";\nimport { useBackmoji, useImageLoader, useImageRenderer } from \"@backmoji/vue\";\nimport VueLogo from \"/vue.svg\";\n\nconst canvasRef = ref\u003cHTMLCanvasElement\u003e();\nconst image = useImageLoader(VueLogo);\nconst renderer = useImageRenderer(image);\nconst { render } = useBackmoji(canvasRef, renderer, {\n  rowGap: 15,\n  columnGap: 20,\n  degree: 30,\n});\nwatchEffect(() =\u003e {\n  render();\n});\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003ccanvas ref=\"canvasRef\"\u003e\u003c/canvas\u003e\n\u003c/template\u003e\n```\n\n### With React\n\n```tsx\nimport { useBackmoji, useImageLoader, useImageRenderer } from \"@backmoji/react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport ReactLogo from \"/react.svg\";\n\nexport function WithReact() {\n  const canvasRef = useRef\u003cHTMLCanvasElement\u003e(null);\n  const [mounted, setMounted] = useState(false);\n\n  useEffect(() =\u003e {\n    setMounted(true);\n  }, []);\n\n  const img = useImageLoader(ReactLogo);\n  const renderer = useImageRenderer(img);\n  const { render } = useBackmoji(canvasRef, renderer, {\n    rowGap: 15,\n    columnGap: 20,\n    degree: 30,\n  });\n\n  useEffect(() =\u003e {\n    if (mounted) {\n      render();\n    }\n  }, [mounted, render]);\n\n  return \u003ccanvas ref={canvasRef}\u003e\u003c/canvas\u003e;\n}\n```\n\n## Examples\n\n### Render a text pattern\n\nYou can create a text pattern using `createTextRenderer` function exported by `backmoji`.\nYou can adjust the font and size of the text by setting `font` as shown in the code below, and you can also adjust the spacing between the texts and the rotation by setting `rowGap`, `columnGap` and `degree`.\n\n```ts\nimport { backmoji, createTextRenderer } from \"backmoji\";\n\nconst parent = document.querySelector\u003cHTMLDivElement\u003e(\"#demo-text-pattern\")!;\nconst canvas = document.createElement(\"canvas\");\n\nconst renderer = createTextRenderer(\"Hello World!\", {\n  font: \"30px Aria\",\n});\nconst height = 160;\nconst { render, setSize } = backmoji(canvas, renderer, {\n  degree: -45,\n  rowGap: 50,\n  columnGap: 40,\n});\n\nsetSize(parent.clientWidth, height);\n\nconst observer = new ResizeObserver((entries) =\u003e {\n  for (const entry of entries) {\n    setSize(entry.contentRect.width, height);\n    render();\n  }\n});\nobserver.observe(parent);\n\nparent.appendChild(canvas);\nrender();\n```\n\nAlso, you can render an emoji for emoji is a type of text.\n\n### Render an image pattern\n\nRendering an image is similar to rendering text. You can create an image pattern using `createImageRenderer`. `createImageRenderer` accepts an image element. Make sure the image has loaded before the first render.\n\n```ts\nimport { backmoji, createImageRenderer } from \"backmoji\";\nimport JSLogo from \"/javascript.png\";\n\nconst canvas = document.createElement(\"canvas\");\nconst parent = document.querySelector\u003cHTMLDivElement\u003e(\"#demo-image-pattern\")!;\n\nfunction loadImage(url: string) {\n  const image = new Image();\n  image.src = url;\n  return new Promise\u003cHTMLImageElement\u003e((resolve) =\u003e {\n    image.onload = () =\u003e resolve(image);\n  });\n}\n\nasync function createRenderer() {\n  return createImageRenderer(await loadImage(JSLogo));\n}\n\nconst height = 300;\nconst { render, setSize } = backmoji(canvas, createRenderer(), {\n  degree: -30,\n  rowGap: 50,\n  columnGap: 60,\n});\n\nconst observer = new ResizeObserver((entries) =\u003e {\n  for (const entry of entries) {\n    setSize(entry.contentRect.width, height);\n    render();\n  }\n});\nobserver.observe(parent);\n\nparent.appendChild(canvas);\nrender();\n```\n\n### With custom render function\n\nSometimes you may need to render patterns with special arrangements, such as a chessboard-like staggered arrangement. You can use the second parameter `options.customRender` provided by `createTextRenderer` or `createImageRenderer` to handle the rendering position yourself.\n\n```ts\nimport { backmoji, createImageRenderer } from \"backmoji\";\nimport JSLogo from \"/javascript.png\";\n\nconst canvas = document.createElement(\"canvas\");\nconst parent = document.querySelector\u003cHTMLDivElement\u003e(\"#demo-custom-pattern\")!;\n\nfunction loadImage(url: string) {\n  const image = new Image();\n  image.src = url;\n  return new Promise\u003cHTMLImageElement\u003e((resolve) =\u003e {\n    image.onload = () =\u003e resolve(image);\n  });\n}\n\nasync function createRenderer() {\n  return createImageRenderer(await loadImage(JSLogo), {\n    customRender({ ctx, item, renderItemWidth, renderItemHeight, rowGap, columnGap, columnCount, rowCount }) {\n      for (let rowIndex = 0; rowIndex \u003c rowCount; rowIndex++) {\n        let from: number, to: number;\n        if (rowIndex % 2 === 0) {\n          from = -2;\n          to = columnCount;\n        } else {\n          from = 0;\n          to = columnCount + 2;\n        }\n        for (let columnIndex = from; columnIndex \u003c to; columnIndex++) {\n          const x = columnIndex * (renderItemWidth + columnGap);\n          const y = rowIndex * (renderItemHeight + rowGap);\n          if ((columnIndex - rowIndex) % 2 === 0) {\n            ctx.drawImage(item, x, y, renderItemWidth, renderItemHeight);\n          }\n        }\n      }\n    },\n  });\n}\n\nconst height = 300;\nconst { render, setSize } = backmoji(canvas, createRenderer());\n\nconst observer = new ResizeObserver((entries) =\u003e {\n  for (const entry of entries) {\n    setSize(entry.contentRect.width, height);\n    render();\n  }\n});\nobserver.observe(parent);\n\nparent.appendChild(canvas);\nrender();\n```\n\n### With animation\n\n`backmoji` itself does not provide any animation-related functionality, but through `customRender`, you can implement your own animation functionality, just like the animated background on this website. Here is a demonstration related to animation functionality.\n\n```ts\n// animation.ts\nimport { isDefined } from \"@bernankez/utils\";\nimport Stats from \"stats.js\";\n\nexport type Fn = () =\u003e void;\n\nexport function animate(options?: {\n  speed?: number;\n}) {\n  let { speed: _speed = 0.5 } = options || {};\n\n  let _callback: Fn | undefined;\n  let _isPlay = false;\n  let _timestamp = 0;\n  let _ref: number | undefined;\n\n  const _stats = new Stats();\n  _stats.showPanel(0);\n\n  function getTimestamp() {\n    return _timestamp;\n  }\n\n  function setSpeed(speed: number) {\n    _speed = speed;\n  }\n\n  function setCallback(callback: Fn) {\n    _callback = callback;\n  }\n\n  function toggle(play?: boolean) {\n    const isPlay = isDefined(play) ? play : !_isPlay;\n    if (isPlay === _isPlay) {\n      return;\n    }\n    if (isPlay) {\n      _play();\n    } else {\n      _pause();\n    }\n    _isPlay = isPlay;\n  }\n\n  function _play() {\n    if (!_callback) {\n      return;\n    }\n    _timestamp += _speed;\n    _stats.begin();\n    _callback();\n    _stats.end();\n    _ref = requestAnimationFrame(_play);\n  }\n\n  function _pause() {\n    if (_ref) {\n      cancelAnimationFrame(_ref);\n      _ref = undefined;\n    }\n  }\n\n  function _reset() {\n    _timestamp = 0;\n    _pause();\n    _callback?.();\n  }\n\n  return {\n    stat: _stats,\n\n    getTimestamp,\n\n    setSpeed,\n    setCallback,\n\n    toggle,\n    play: _play,\n    pause: _pause,\n    reset: _reset,\n  };\n}\n```\n\n```ts\nimport { backmoji, createImageRenderer } from \"backmoji\";\nimport { animate } from \"@/utils/animation\";\nimport Paw from \"/paw.svg\";\n\nconst { play, setCallback, getTimestamp, stat } = animate({\n  speed: 0.3,\n});\n\nconst canvas = document.createElement(\"canvas\");\nconst parent = document.querySelector\u003cHTMLDivElement\u003e(\"#demo-animation-pattern\")!;\n\nfunction loadImage(url: string) {\n  const image = new Image();\n  image.src = url;\n  return new Promise\u003cHTMLImageElement\u003e((resolve) =\u003e {\n    image.onload = () =\u003e resolve(image);\n  });\n}\n\nasync function createRenderer() {\n  return createImageRenderer(await loadImage(Paw), {\n    customRender({ ctx, item, renderItemWidth, renderItemHeight, rowGap, columnGap, columnCount, rowCount }) {\n      for (let rowIndex = 0; rowIndex \u003c rowCount; rowIndex++) {\n        let from: number, to: number;\n        if (rowIndex % 2 === 0) {\n          from = -2;\n          to = columnCount;\n        } else {\n          from = 0;\n          to = columnCount + 2;\n        }\n        for (let columnIndex = from; columnIndex \u003c to; columnIndex++) {\n          let offset = getTimestamp();\n          offset = offset % (2 * (renderItemWidth + columnGap));\n          const x = columnIndex * (renderItemWidth + columnGap) + (rowIndex % 2 === 0 ? 1 : -1) * offset;\n          const y = rowIndex * (renderItemHeight + rowGap);\n          if ((columnIndex - rowIndex) % 2 === 0) {\n            ctx.drawImage(item, x, y, renderItemWidth, renderItemHeight);\n          }\n        }\n      }\n    },\n  });\n}\n\nconst height = 300;\nconst { render, setSize } = backmoji(canvas, createRenderer(), {\n  degree: 30,\n});\n\nfunction animationCb() {\n  const ctx = canvas.getContext(\"2d\")!;\n  const w = canvas.width;\n  const h = canvas.height;\n  ctx.clearRect(0, 0, w, h);\n  render();\n}\n\nsetCallback(animationCb);\n\nconst observer = new ResizeObserver((entries) =\u003e {\n  for (const entry of entries) {\n    setSize(entry.contentRect.width, height);\n    render();\n  }\n});\nobserver.observe(parent);\n\nparent.appendChild(canvas);\nconst statDom = stat.dom;\nstatDom.style.position = \"absolute\";\nstatDom.style.top = \"0\";\nstatDom.style.right = \"0\";\nstatDom.style.left = \"unset\";\nparent.appendChild(statDom);\n\nplay();\n```\n\n## Customize\n\nYou can create your own `renderer` if the provided renderers do not meet your needs. See [renderer.ts](https://github.com/Bernankez/Backmoji/blob/master/packages/core/src/renderer.ts) for more details.\n\n## Options\n\n### `backmoji`\n\n```ts\ndeclare function backmoji(canvas: Awaitable\u003cHTMLCanvasElement\u003e, renderer: Awaitable\u003cRenderer\u003e, options?: BackmojiOptions): {\n  render: () =\u003e Promise\u003cvoid\u003e;\n  setOptions: (options: BackmojiOptions, combine?: boolean) =\u003e void;\n  /*\n  * Set the size of the canvas. It handles device pixel ratio automatically.\n  */\n  setSize: (width: number | undefined, height: number | undefined) =\u003e Promise\u003cvoid\u003e;\n};\n\ninterface BackmojiOptions {\n  degree?: number;\n  rowGap?: number;\n  columnGap?: number;\n}\n\ntype Renderer = (context: RendererContext) =\u003e void;\n\ninterface RendererContext {\n  ctx: CanvasRenderingContext2D;\n  width: number;\n  height: number;\n  rowGap: number;\n  columnGap: number;\n  degree: number;\n  angle: number;\n  /*\n  * Measure size of texts, using CanvasRenderingContext2D.measureText underhood\n  */\n  measureText: (text: string) =\u003e {\n    width: number;\n    fontHeight: number;\n    height: number;\n  };\n  /*\n  * Calculate how many items should be rendered each row\n  */\n  calculateRenderCount: (renderItemWidth: number, renderItemHeight: number) =\u003e number[] \u0026 {\n    row: number;\n    column: number;\n  };\n  /*\n  * Calculate the translate after rotation\n  */\n  calculateTranslate: () =\u003e number[] \u0026 {\n    x: number;\n    y: number;\n  };\n}\n```\n\n### renderer\n\n```ts\ninterface CreateTextRendererOptions {\n  font?: string;\n  customRender?: (context: RenderContext\u003cstring\u003e) =\u003e void;\n}\n/*\n* The default render function if no customRender provided\n*/\ndeclare function textRender(context: RenderContext\u003cstring\u003e): void;\ndeclare function createTextRenderer(text: string, options?: CreateTextRendererOptions): Renderer;\ninterface CreateImageRendererOptions {\n  customRender?: (context: RenderContext\u003cHTMLImageElement\u003e) =\u003e void;\n}\n```\n\n```ts\n/*\n* The default render function if no customRender provided\n*/\ndeclare function imageRender(context: RenderContext\u003cHTMLImageElement\u003e): void;\ndeclare function createImageRenderer(img: HTMLImageElement, options?: CreateImageRendererOptions): Renderer;\n```\n\nThe following image explains the meanings of various attribute values:\n\n![options](https://github.com/Bernankez/Backmoji/assets/23058788/cba65b1c-7885-403c-b33d-730b1310e458)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbernankez%2Fbackmoji","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbernankez%2Fbackmoji","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbernankez%2Fbackmoji/lists"}