Skip to content

Feat/lanyard front back images#977

Merged
DavidHDev merged 12 commits into
DavidHDev:mainfrom
EnderRomantice:feat/lanyard-front-back-images
Jun 8, 2026
Merged

Feat/lanyard front back images#977
DavidHDev merged 12 commits into
DavidHDev:mainfrom
EnderRomantice:feat/lanyard-front-back-images

Conversation

@EnderRomantice
Copy link
Copy Markdown
Contributor

@EnderRomantice EnderRomantice commented Jun 7, 2026

Summary

When I was using the Lanyard component, I noticed that it was not very convenient to customize the card artwork and the lanyard band texture.

This PR adds new props for customizing the card's front face, back face, and band texture:

  • frontImage

  • backImage

  • imageFit

  • lanyardImage

  • lanyardWidth

The front and back card images are rendered independently and preserve their original aspect ratio, so they will not be stretched or distorted. The lanyard band can also use a custom texture, with an adjustable width to give custom artwork more visible space.

Motivation

The card model uses a single texture atlas. The front face is UV-mapped to the left half of the texture, and the back face is UV-mapped to the right half.

Because of this, simply replacing the card material's map with a custom image does not work correctly. The image can bleed across both sides of the card, or appear cropped and zoomed, because each face only samples part of the atlas.

This PR solves that by compositing the custom front and back images into the correct regions of the original texture atlas.

The lanyard band also benefits from more flexible customization. Custom band textures can now be passed in directly, and lanyardWidth can be adjusted to give the artwork more room to display clearly.

New Props

PropTypeDefaultDescription
frontImagestring | nullnullImage URL for the card's front face.
backImagestring | nullnullImage URL for the card's back face, rendered independently from the front.
imageFit'cover' | 'contain''cover'How a custom card image fits its face. Both preserve aspect ratio — cover fills and crops, contain letterboxes.
lanyardImagestring | nullnullImage URL for the lanyard band's texture.
lanyardWidthnumber1Width of the lanyard band. Increase it to give custom band artwork more room and reduce stretching.

Example

<Lanyard
  position={[0, 0, 20]}
  gravity={[0, -40, 0]}
  frontImage="/avatar.png"
  backImage="/company-logo.png"
  imageFit="cover"
  lanyardImage="/band.png"
  lanyardWidth={1.8}
/>

How It Works

For the card, the front and back UV regions were measured from card.glb. The front face uses the left half of the texture, and the back face uses the right half.

When frontImage or backImage is provided, the component creates a new CanvasTexture at the atlas resolution. It first draws the original baked texture, preserving the card edges and any untouched areas. Then it draws each custom image into its corresponding region of the atlas.

The card images are drawn with aspect-ratio preserving behavior:

  • cover fills the target area and may crop part of the image.

  • contain keeps the full image visible and may leave empty space.

For the lanyard band, lanyardImage is used as the band texture, and lanyardWidth controls the visual width of the band.

Backwards Compatibility

This change does not affect existing usage.

If no custom card image is provided, the component keeps using the original baked card texture. lanyardWidth also defaults to 1, so the existing demo should render the same as before.

Notes

  • Card and band images should use raster formats such as PNG or JPG.

  • SVG images may not work reliably when drawn onto a canvas, so they may be skipped.

  • The changes have been applied consistently across all four variants: JS/CSS, JS/Tailwind, TS/CSS, and TS/Tailwind.

  • The demo prop table and usage snippet have also been updated.

Commits

The changes are split into separate commits for easier review. It's currently working fine in my own project.

If you have any better suggestions for this feature, please let me know, and I will continue to make changes. Thank you.

2026-06-07.20.26.53.mov

Replace the single `cardImage` prop with `frontImage` and `backImage`, plus
an `imageFit` ('cover' | 'contain') option. The card model's atlas maps the
front face to the left half and the back face to the right half, so each
image is composited into its own half of a canvas texture and the two faces
render independently. Images are drawn aspect-preserving (no stretching).
When no image is supplied, the original baked texture is returned unchanged,
so default rendering is untouched.
Mirror the front/back compositing API (frontImage, backImage, imageFit) in
the JavaScript + Tailwind variant. Default rendering is preserved when no
custom image is provided.
Mirror the front/back compositing API (frontImage, backImage, imageFit) in
the TypeScript + CSS variant, with prop types on LanyardProps and BandProps.
Default rendering is preserved when no custom image is provided.
Mirror the front/back compositing API (frontImage, backImage, imageFit) in
the TypeScript + Tailwind variant. Default rendering is preserved when no
custom image is provided.
Expose the band's meshline lineWidth as a `lanyardWidth` prop (default 1).
Widening the band gives a custom band image more room and reduces texture
stretching. Default value preserves the current appearance.
Mirror the lanyardWidth prop (maps to meshline lineWidth, default 1) in the
JavaScript + Tailwind variant.
Mirror the lanyardWidth prop (maps to meshline lineWidth, default 1) in the
TypeScript + CSS variant, typed on LanyardProps and BandProps.
Mirror the lanyardWidth prop (maps to meshline lineWidth, default 1) in the
TypeScript + Tailwind variant.
@DavidHDev
Copy link
Copy Markdown
Owner

Pretty nice, thanks!

@DavidHDev DavidHDev merged commit 70356e2 into DavidHDev:main Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants