Warning
This library is still under development, and the APIs are not stable yet.
npm install backmojiimport{backmoji,createImageRenderer}from"backmoji";importJSLogofrom"/javascript.png";constcanvas=document.createElement("canvas");document.body.appendChild(canvas);asyncfunctionloadImage(url){constimage=newImage();image.src=url;returnnewPromise()((resolve)=>{image.onload=()=>resolve(image);});}asyncfunctioncreateRenderer(){returncreateImageRenderer(awaitloadImage(JSLogo));}const{ render }=backmoji(canvas,createRenderer(),{rowGap: 15,columnGap: 20,degree: 30,});render();<script setup lang="ts">import { ref, watchEffect } from"vue";import { useBackmoji, useImageLoader, useImageRenderer } from"@backmoji/vue";importVueLogofrom"/vue.svg";const canvasRef =ref<HTMLCanvasElement>();const image =useImageLoader(VueLogo);const renderer =useImageRenderer(image);const { render } =useBackmoji(canvasRef, renderer, { rowGap: 15, columnGap: 20, degree: 30,});watchEffect(() => {render();});</script>
<template>
<canvasref="canvasRef"></canvas>
</template>import{useBackmoji,useImageLoader,useImageRenderer}from"@backmoji/react";import{useEffect,useRef,useState}from"react";importReactLogofrom"/react.svg";exportfunctionWithReact(){constcanvasRef=useRef<HTMLCanvasElement>(null);const[mounted,setMounted]=useState(false);useEffect(()=>{setMounted(true);},[]);constimg=useImageLoader(ReactLogo);constrenderer=useImageRenderer(img);const{ render }=useBackmoji(canvasRef,renderer,{rowGap: 15,columnGap: 20,degree: 30,});useEffect(()=>{if(mounted){render();}},[mounted,render]);return<canvasref={canvasRef}></canvas>;}You can create a text pattern using createTextRenderer function exported by backmoji.
You 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.
import{backmoji,createTextRenderer}from"backmoji";constparent=document.querySelector<HTMLDivElement>("#demo-text-pattern")!;constcanvas=document.createElement("canvas");constrenderer=createTextRenderer("Hello World!",{font: "30px Aria",});constheight=160;const{ render, setSize }=backmoji(canvas,renderer,{degree: -45,rowGap: 50,columnGap: 40,});setSize(parent.clientWidth,height);constobserver=newResizeObserver((entries)=>{for(constentryofentries){setSize(entry.contentRect.width,height);render();}});observer.observe(parent);parent.appendChild(canvas);render();Also, you can render an emoji for emoji is a type of text.
Rendering 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.
import{backmoji,createImageRenderer}from"backmoji";importJSLogofrom"/javascript.png";constcanvas=document.createElement("canvas");constparent=document.querySelector<HTMLDivElement>("#demo-image-pattern")!;functionloadImage(url: string){constimage=newImage();image.src=url;returnnewPromise<HTMLImageElement>((resolve)=>{image.onload=()=>resolve(image);});}asyncfunctioncreateRenderer(){returncreateImageRenderer(awaitloadImage(JSLogo));}constheight=300;const{ render, setSize }=backmoji(canvas,createRenderer(),{degree: -30,rowGap: 50,columnGap: 60,});constobserver=newResizeObserver((entries)=>{for(constentryofentries){setSize(entry.contentRect.width,height);render();}});observer.observe(parent);parent.appendChild(canvas);render();Sometimes 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.
import{backmoji,createImageRenderer}from"backmoji";importJSLogofrom"/javascript.png";constcanvas=document.createElement("canvas");constparent=document.querySelector<HTMLDivElement>("#demo-custom-pattern")!;functionloadImage(url: string){constimage=newImage();image.src=url;returnnewPromise<HTMLImageElement>((resolve)=>{image.onload=()=>resolve(image);});}asyncfunctioncreateRenderer(){returncreateImageRenderer(awaitloadImage(JSLogo),{customRender({ ctx, item, renderItemWidth, renderItemHeight, rowGap, columnGap, columnCount, rowCount }){for(letrowIndex=0;rowIndex<rowCount;rowIndex++){letfrom: number,to: number;if(rowIndex%2===0){from=-2;to=columnCount;}else{from=0;to=columnCount+2;}for(letcolumnIndex=from;columnIndex<to;columnIndex++){constx=columnIndex*(renderItemWidth+columnGap);consty=rowIndex*(renderItemHeight+rowGap);if((columnIndex-rowIndex)%2===0){ctx.drawImage(item,x,y,renderItemWidth,renderItemHeight);}}}},});}constheight=300;const{ render, setSize }=backmoji(canvas,createRenderer());constobserver=newResizeObserver((entries)=>{for(constentryofentries){setSize(entry.contentRect.width,height);render();}});observer.observe(parent);parent.appendChild(canvas);render();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.
// animation.tsimport{isDefined}from"@bernankez/utils";importStatsfrom"stats.js";exporttypeFn=()=>void;exportfunctionanimate(options?: {speed?: number;}){let{speed: _speed=0.5}=options||{};let_callback: Fn|undefined;let_isPlay=false;let_timestamp=0;let_ref: number|undefined;const_stats=newStats();_stats.showPanel(0);functiongetTimestamp(){return_timestamp;}functionsetSpeed(speed: number){_speed=speed;}functionsetCallback(callback: Fn){_callback=callback;}functiontoggle(play?: boolean){constisPlay=isDefined(play) ? play : !_isPlay;if(isPlay===_isPlay){return;}if(isPlay){_play();}else{_pause();}_isPlay=isPlay;}function_play(){if(!_callback){return;}_timestamp+=_speed;_stats.begin();_callback();_stats.end();_ref=requestAnimationFrame(_play);}function_pause(){if(_ref){cancelAnimationFrame(_ref);_ref=undefined;}}function_reset(){_timestamp=0;_pause();_callback?.();}return{stat: _stats,
getTimestamp,
setSpeed,
setCallback,
toggle,play: _play,pause: _pause,reset: _reset,};}import{backmoji,createImageRenderer}from"backmoji";import{animate}from"@/utils/animation";importPawfrom"/paw.svg";const{ play, setCallback, getTimestamp, stat }=animate({speed: 0.3,});constcanvas=document.createElement("canvas");constparent=document.querySelector<HTMLDivElement>("#demo-animation-pattern")!;functionloadImage(url: string){constimage=newImage();image.src=url;returnnewPromise<HTMLImageElement>((resolve)=>{image.onload=()=>resolve(image);});}asyncfunctioncreateRenderer(){returncreateImageRenderer(awaitloadImage(Paw),{customRender({ ctx, item, renderItemWidth, renderItemHeight, rowGap, columnGap, columnCount, rowCount }){for(letrowIndex=0;rowIndex<rowCount;rowIndex++){letfrom: number,to: number;if(rowIndex%2===0){from=-2;to=columnCount;}else{from=0;to=columnCount+2;}for(letcolumnIndex=from;columnIndex<to;columnIndex++){letoffset=getTimestamp();offset=offset%(2*(renderItemWidth+columnGap));constx=columnIndex*(renderItemWidth+columnGap)+(rowIndex%2===0 ? 1 : -1)*offset;consty=rowIndex*(renderItemHeight+rowGap);if((columnIndex-rowIndex)%2===0){ctx.drawImage(item,x,y,renderItemWidth,renderItemHeight);}}}},});}constheight=300;const{ render, setSize }=backmoji(canvas,createRenderer(),{degree: 30,});functionanimationCb(){constctx=canvas.getContext("2d")!;constw=canvas.width;consth=canvas.height;ctx.clearRect(0,0,w,h);render();}setCallback(animationCb);constobserver=newResizeObserver((entries)=>{for(constentryofentries){setSize(entry.contentRect.width,height);render();}});observer.observe(parent);parent.appendChild(canvas);conststatDom=stat.dom;statDom.style.position="absolute";statDom.style.top="0";statDom.style.right="0";statDom.style.left="unset";parent.appendChild(statDom);play();You can create your own renderer if the provided renderers do not meet your needs. See renderer.ts for more details.
declarefunctionbackmoji(canvas: Awaitable<HTMLCanvasElement>,renderer: Awaitable<Renderer>,options?: BackmojiOptions): {render: ()=>Promise<void>;setOptions: (options: BackmojiOptions,combine?: boolean)=>void;/* * Set the size of the canvas. It handles device pixel ratio automatically. */setSize: (width: number|undefined,height: number|undefined)=>Promise<void>;};interfaceBackmojiOptions{degree?: number;rowGap?: number;columnGap?: number;}typeRenderer=(context: RendererContext)=>void;interfaceRendererContext{ctx: CanvasRenderingContext2D;width: number;height: number;rowGap: number;columnGap: number;degree: number;angle: number;/* * Measure size of texts, using CanvasRenderingContext2D.measureText underhood */measureText: (text: string)=>{width: number;fontHeight: number;height: number;};/* * Calculate how many items should be rendered each row */calculateRenderCount: (renderItemWidth: number,renderItemHeight: number)=>number[]&{row: number;column: number;};/* * Calculate the translate after rotation */calculateTranslate: ()=>number[]&{x: number;y: number;};}interfaceCreateTextRendererOptions{font?: string;customRender?: (context: RenderContext<string>)=>void;}/** The default render function if no customRender provided*/declarefunctiontextRender(context: RenderContext<string>): void;declarefunctioncreateTextRenderer(text: string,options?: CreateTextRendererOptions): Renderer;interfaceCreateImageRendererOptions{customRender?: (context: RenderContext<HTMLImageElement>)=>void;}/** The default render function if no customRender provided*/declarefunctionimageRender(context: RenderContext<HTMLImageElement>): void;declarefunctioncreateImageRenderer(img: HTMLImageElement,options?: CreateImageRendererOptions): Renderer;The following image explains the meanings of various attribute values:
