{"id":15384459,"url":"https://github.com/danielgamage/lenti","last_synced_at":"2025-04-15T19:42:03.825Z","repository":{"id":143885633,"uuid":"83950903","full_name":"danielgamage/lenti","owner":"danielgamage","description":"Lenticular image viewer","archived":false,"fork":false,"pushed_at":"2025-03-25T15:18:40.000Z","size":43384,"stargazers_count":11,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-29T00:23:25.613Z","etag":null,"topics":["canvas","image-viewer","lenticular","slideshow"],"latest_commit_sha":null,"homepage":"https://danielgamage.github.io/lenti/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/danielgamage.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":"2017-03-05T06:45:51.000Z","updated_at":"2025-03-03T05:40:31.000Z","dependencies_parsed_at":null,"dependency_job_id":"46aae2e7-ded3-4874-8044-316db9513693","html_url":"https://github.com/danielgamage/lenti","commit_stats":{"total_commits":56,"total_committers":1,"mean_commits":56.0,"dds":0.0,"last_synced_commit":"060f4e93b6858072a79d073f791bddf806ae723a"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielgamage%2Flenti","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielgamage%2Flenti/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielgamage%2Flenti/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielgamage%2Flenti/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danielgamage","download_url":"https://codeload.github.com/danielgamage/lenti/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249140560,"owners_count":21219320,"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","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":["canvas","image-viewer","lenticular","slideshow"],"created_at":"2024-10-01T14:42:04.421Z","updated_at":"2025-04-15T19:42:03.806Z","avatar_url":"https://github.com/danielgamage.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"**lenti v0.3.2**\n\n***\n\n\u003ca href=\"https://npmjs.org/package/lenti\"\u003e\n  \u003cimg src=\"https://badge.fury.io/js/lenti.svg\" alt=\"NPM version\"/\u003e\n\u003c/a\u003e\n\nLenti is an image viewer that mimicks the effect of lenticular printing.\nIt displays images in a canvas element and binds events for mouse and accelerometer events,\nso just as you would rotate a card or print with lenticular lenses on it, you can tilt your phone to transition between images.\n\n**[Demo](https://lenti.vercel.app/)**\n\n## Installation\n```sh\nnpm install --save lenti\n```\n\n## Usage\nLenti will accomodate any number of images in the container.\n\n```html\n\u003cdiv data-lenticular-list=\"true\"\u003e\n  \u003cimg src=\"./images/sample_a_1.png\" alt=\"Left-facing view of object\" width=\"1280\" height=\"720\" /\u003e\n  \u003cimg src=\"./images/sample_a_2.png\" alt=\"Center-left-facing view of object\" width=\"1280\" height=\"720\" /\u003e\n  \u003cimg src=\"./images/sample_a_3.png\" alt=\"Center-facing view of object\" width=\"1280\" height=\"720\" /\u003e\n  \u003cimg src=\"./images/sample_a_4.png\" alt=\"Center-right-facing view of object\" width=\"1280\" height=\"720\" /\u003e\n  \u003cimg src=\"./images/sample_a_5.png\" alt=\"Right-facing view of object\" width=\"1280\" height=\"720\" /\u003e\n\u003c/div\u003e\n```\n```ts\nimport {Lenti, bindGyroscopeXY, bindMouseXY} from \"lenti\"\n\nconst container = document.querySelector(\"[data-lenticular-list]\")\nconst canvas = document.createElement(\"canvas\") // programmatically creating canvas. could also just put it in the HTML\ncontainer.appendChild(canvas)\nconst lenti = new Lenti({\n  container,\n  canvas,\n  images: Array.from(container.querySelectorAll(\"img\")),\n  uiAdapters: [bindMouseXY({eventRoot: window}), bindGyroscopeXY()]\n})\n```\n\n## Browser Support\n\nLenti relies on WebGPU for buttery-smooth framerates on modern computers. Unflagged browser support below:\n\n\u003cpicture\u003e\n  \u003csource type=\"image/webp\" srcset=\"https://caniuse.bitsofco.de/image/webgpu.webp\"\u003e\n  \u003csource type=\"image/png\" srcset=\"https://caniuse.bitsofco.de/image/webgpu.png\"\u003e\n  \u003cimg src=\"https://caniuse.bitsofco.de/image/webgpu.jpg\" alt=\"Data on support for the webgpu feature across the major browsers from caniuse.com\"\u003e\n\u003c/picture\u003e\n\n_Thanks to Ire for their [Can I Use Embed](https://caniuse.bitsofco.de/#how-to-use)_\n\n# API\n\n## Lenti\n\n### Lenti\n\nTODOs:\n- [ ] Add support for pivotted x/y values\n- [ ] Add support for touch events\n- [ ] MSAA instead of just rendering higher resolutions https://webgpufundamentals.org/webgpu/lessons/webgpu-multisampling.html#a-multisampling\n\n#### Constructors\n\n##### new Lenti()\n\n```ts\nnew Lenti(options: {\n  canvas: HTMLCanvasElement;\n  images: HTMLImageElement[];\n  oversampling: number;\n  samplerSettings: GPUSamplerDescriptor;\n  uiAdapters: UIAdapter[];\n } \u0026 Partial\u003c{\n  lensDarkening: number;\n  lensDistortion: number;\n  stripWidth: number;\n  transition: number;\n  viewX: number;\n  viewY: number;\n }\u003e): Lenti\n```\n\n###### Parameters\n\n###### options\n\n\\{\n  `canvas`: [`HTMLCanvasElement`](https://developer.mozilla.org/docs/Web/API/HTMLCanvasElement);\n  `images`: [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)[];\n  `oversampling`: `number`;\n  `samplerSettings`: `GPUSamplerDescriptor`;\n  `uiAdapters`: [`UIAdapter`](README.md#uiadapter)[];\n \\} \u0026 [`Partial`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype)\\\u003c\\{\n  `lensDarkening`: `number`;\n  `lensDistortion`: `number`;\n  `stripWidth`: `number`;\n  `transition`: `number`;\n  `viewX`: `number`;\n  `viewY`: `number`;\n \\}\\\u003e\n\n###### Returns\n\n[`Lenti`](README.md#lenti)\n\n#### Properties\n\n| Property | Type | Default value | Description |\n| ------ | ------ | ------ | ------ |\n| \u003ca id=\"canvas-1\"\u003e\u003c/a\u003e `canvas` | [`HTMLCanvasElement`](https://developer.mozilla.org/docs/Web/API/HTMLCanvasElement) | `null` | The output (rendered) canvas |\n| \u003ca id=\"images-1\"\u003e\u003c/a\u003e `images` | ( \\| [`HTMLCanvasElement`](https://developer.mozilla.org/docs/Web/API/HTMLCanvasElement) \\| `HTMLOrSVGImageElement` \\| [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement) \\| [`ImageBitmap`](https://developer.mozilla.org/docs/Web/API/ImageBitmap) \\| [`OffscreenCanvas`](https://developer.mozilla.org/docs/Web/API/OffscreenCanvas) \\| [`ImageData`](https://developer.mozilla.org/docs/Web/API/ImageData))[] | `[]` | Image elements to pull textures from. Also supports ImageData. |\n| \u003ca id=\"inputs-1\"\u003e\u003c/a\u003e `inputs` | \\{ `lensDarkening`: `number`; `lensDistortion`: `number`; `stripWidth`: `number`; `transition`: `number`; `viewX`: `number`; `viewY`: `number`; \\} | `undefined` | - |\n| `inputs.lensDarkening` | `number` | `undefined` | Amount of darkening to apply near the virtual off-axis parts of the lenticule |\n| `inputs.lensDistortion` | `number` | `undefined` | Amount of y-axis distortion applied to the lenticule simulate vertical off-axis viewing |\n| `inputs.stripWidth` | `number` | `undefined` | Image-space width of the strip placed in an interlaced array under the lenticule |\n| `inputs.transition` | `number` | `undefined` | Amount of virtual warping to apply to the transition from left–right |\n| `inputs.viewX` | `number` | `undefined` | [0: Leftmost image, 1: Rightmost image] |\n| `inputs.viewY` | `number` | `undefined` | [0: Top distortion, 1: Bottom distortion] |\n| \u003ca id=\"isvisible-1\"\u003e\u003c/a\u003e `isVisible` | `boolean` | `false` | Whether the canvas is visible in the viewport |\n| \u003ca id=\"oversampling-1\"\u003e\u003c/a\u003e `oversampling` | `number` | `2` | Canvas oversampling |\n| \u003ca id=\"uiadaptercleanupcallbacks-1\"\u003e\u003c/a\u003e `uiAdapterCleanupCallbacks` | [`UIAdapterCleanupCallback`](README.md#uiadaptercleanupcallback)[] | `[]` | - |\n| \u003ca id=\"uiadapters-1\"\u003e\u003c/a\u003e `uiAdapters` | [`UIAdapter`](README.md#uiadapter)[] | `undefined` | UI adapters connect user input to the shader settings, custom adapters can be made **Default** `[bindMouseXY(), bindGyroscopeXY()]` |\n\n#### Accessors\n\n##### imageAspectRatio\n\n###### Get Signature\n\n```ts\nget imageAspectRatio(): number\n```\n\n###### Returns\n\n`number`\n\n#### Methods\n\n##### createTextureFromImage()\n\n```ts\ncreateTextureFromImage(imageData: ImageData): Promise\u003cGPUTexture\u003e\n```\n\n###### Parameters\n\n###### imageData\n\n[`ImageData`](https://developer.mozilla.org/docs/Web/API/ImageData)\n\n###### Returns\n\n[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\\\u003c[`GPUTexture`](https://developer.mozilla.org/docs/Web/API/GPUTexture)\\\u003e\n\n***\n\n##### destroy()\n\n```ts\ndestroy(): void\n```\n\nDestroys the instance and cleans up resources\n\n###### Returns\n\n`void`\n\n***\n\n##### error()\n\n```ts\nerror(e: Error): void\n```\n\nCommon Lenti errors may be fired from here\n\n###### Parameters\n\n###### e\n\n[`Error`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error)\n\n###### Returns\n\n`void`\n\n***\n\n##### init()\n\n```ts\ninit(): Promise\u003cvoid\u003e\n```\n\n###### Returns\n\n[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\\\u003c`void`\\\u003e\n\n***\n\n##### render()\n\n```ts\nrender(): void\n```\n\nRenders the current state of the instance\n\n###### Returns\n\n`void`\n\n***\n\n##### update()\n\n```ts\nupdate(updates: Partial\u003c{\n  lensDarkening: number;\n  lensDistortion: number;\n  stripWidth: number;\n  transition: number;\n  viewX: number;\n  viewY: number;\n }\u003e): void\n```\n\nUpdates the instance state with a subset of Lenti's state\n\n###### Parameters\n\n###### updates\n\n[`Partial`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype)\\\u003c\\{\n  `lensDarkening`: `number`;\n  `lensDistortion`: `number`;\n  `stripWidth`: `number`;\n  `transition`: `number`;\n  `viewX`: `number`;\n  `viewY`: `number`;\n \\}\\\u003e\n\n###### Returns\n\n`void`\n\n***\n\n### bindGyroscopeXYOptions\n\n#### Properties\n\n| Property | Type | Description |\n| ------ | ------ | ------ |\n| \u003ca id=\"usergestureelement-1\"\u003e\u003c/a\u003e `userGestureElement?` | [`HTMLElement`](https://developer.mozilla.org/docs/Web/API/HTMLElement) | Some browsers require user gesture before requesting permission. This is the element that will require click if so. By default, this is the Lenti instance's canvas element, but it can be a button or other interactive element. |\n| \u003ca id=\"xbounds-1\"\u003e\u003c/a\u003e `xBounds` | \\[`number`, `number`\\] | Range of angles in object-space x-rotation |\n| \u003ca id=\"ybounds-1\"\u003e\u003c/a\u003e `yBounds` | \\[`number`, `number`\\] | Range of angles in object-space y-rotation |\n\n***\n\n### Degree\n\n```ts\ntype Degree = number;\n```\n\nA number representing degrees (typically [-360, 360])\n\n***\n\n### NormalizedNumber\n\n```ts\ntype NormalizedNumber = number;\n```\n\nA number in the range [0, 1]\n\n***\n\n### PositiveInteger\n\n```ts\ntype PositiveInteger = number;\n```\n\nA positive integer\n\n## Managing User Interaction\n\n### UIAdapter()\n\n```ts\ntype UIAdapter = (lentiInstance: Lenti) =\u003e UIAdapterCleanupCallback | void;\n```\n\nUI Adapaters listen for events on a page and can access Lenti properties throughout the instance lifecycle.\n\nUIAdapters expect a [Lenti](README.md#lenti) instance to be passed to them, and can be used to bind user input to shader settings.\n\nIn practice, this can be as simple as:\n```ts\n// Sets the lensDarkening based on the amount of daylight when the page loads\nnew Lenti({uiAdapters: [function bindDaylight(lentiInstance: Lenti) =\u003e {\n  lentiInstance.inputs.lensDarkening = 0.5\n})]})\n```\n\n#### Parameters\n\n##### lentiInstance\n\n[`Lenti`](README.md#lenti)\n\n#### Returns\n\n[`UIAdapterCleanupCallback`](README.md#uiadaptercleanupcallback) \\| `void`\n\n***\n\n### UIAdapterCleanupCallback()\n\n```ts\ntype UIAdapterCleanupCallback = () =\u003e void;\n```\n\nUIAdapterCleanupCallback is a function that is called when a Lenti instance is destroyed.\n\nSome cases might be usage in a single-page application where the Lenti instance\nis created and destroyed multiple times as a user navigates the site.\n\n#### Returns\n\n`void`\n\n***\n\n### UIAdapterFactory()\n\n```ts\ntype UIAdapterFactory = (options?: any) =\u003e UIAdapter;\n```\n\nUIAdapterFactory is an initializing function that is passed options for the UIAdapater it contains.\n\nIf you want your UIAdapter to be reusable and configurable, you can create a [UIAdapterFactory](README.md#uiadapterfactory) that returns the [UIAdapter](README.md#uiadapter).\n```ts\n// Sets the lensDarkening based on the amount of daylight when the page loads\nconst bindDaylightFactory = (options: {daylight: number}) =\u003e {\n  return function bindDaylight(lentiInstance: Lenti) =\u003e {\n    lentiInstance.inputs.lensDarkening = 1 - options.daylight\n  }\n}\n\nnew Lenti({uiAdapters: [bindDaylightFactory({daylight: 0.5})]})\n```\n\n#### Parameters\n\n##### options?\n\n`any`\n\n#### Returns\n\n[`UIAdapter`](README.md#uiadapter)\n\n***\n\n### bindGyroscopeXY()\n\n```ts\nfunction bindGyroscopeXY(options: bindGyroscopeXYOptions): UIAdapter\n```\n\nDrives viewX/viewY based on the device viewing angle\n\n#### Parameters\n\n##### options\n\n[`bindGyroscopeXYOptions`](README.md#bindgyroscopexyoptions) = `...`\n\n#### Returns\n\n[`UIAdapter`](README.md#uiadapter)\n\n***\n\n### bindMouseXY()\n\n```ts\nfunction bindMouseXY(options: {\n  eventRoot:   | HTMLElement\n     | Window\n     | Document;\n }): UIAdapter\n```\n\nDrives viewX/viewY based on the mouse position on the element, in the browser window, or in another element (like a touchstrip element)\n\n#### Parameters\n\n##### options\n\n###### eventRoot\n\n  \\| [`HTMLElement`](https://developer.mozilla.org/docs/Web/API/HTMLElement)\n  \\| [`Window`](https://developer.mozilla.org/docs/Web/API/Window)\n  \\| [`Document`](https://developer.mozilla.org/docs/Web/API/Document)\n\nThe element that the mouse will be tracked on\n\n#### Returns\n\n[`UIAdapter`](README.md#uiadapter)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielgamage%2Flenti","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanielgamage%2Flenti","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielgamage%2Flenti/lists"}