Repeat a design and link sides
Two settings decide how a design is placed on the product, and both are yours, never the template’s: whether it repeats as a pattern, and which placements one design drives.
Repeating the design
Section titled “Repeating the design”enableRepeat() lays the template as a tile, again and again, across every
side’s print, or the placements you name. You say which layouts the shopper is offered, which one and what
size it starts at, and what the shopper may change:
designer.enableRepeat({ modes: ['grid', 'brick', 'halfDrop', 'scatter'], mode: 'grid', size: 30, shopperMayChange: true, shopperMayTurnOff: false,});| Option | What it does |
|---|---|
modes |
The layouts offered, at least one: grid lines the tiles up, brick moves every other row along by half a tile, halfDrop moves every other column down by half a tile, and scatter strews them at random places, turns and sizes. Left out, all four. |
mode |
The layout it starts in, one of modes. null starts it not repeated, which needs shopperMayTurnOff. Left out, the first of modes. |
size |
The tile’s width in percent of the print area’s width, above 0 and at most 100. The tile keeps the template’s shape. Left out, 25. |
seed |
Which arrangement a scatter draws, a whole number from 0. Left out, 0. |
shopperMayChange |
Whether your page lets the shopper change the layout, size and arrangement. Left out, true. |
shopperMayTurnOff |
Whether the shopper may turn the repeat off and place the design once. Left out, false. |
placements |
The placements the repeat is laid on, each once, such as a sweater’s back and sleeves while its front is placed once. A placement linked with one named is named too. Left out, every side. |
To offer a repeat from the start, pass the same options as mount()’s
repeat. The designer then draws the pattern in its first frame, where
enableRepeat() after the mount draws the design placed once and then again
as a pattern:
const designer = await Pixonaut.mount(el, { publishableKey: 'pk_live_...', templateId: 'tpl_stripes', repeat: { modes: ['grid', 'brick'], mode: 'brick', size: 30 },});A tile never prints under 10 mm on a side, so on a narrow print area every
size below a share of its width draws the same tile. minSize is that share
on the side shown, at the product’s size: start a size slider there, so every
step of it changes the tile. Another side, variant or size has its own.
The preview lays the pattern as the proof will. getRepeat() answers the
offer and the choice, or null while none is offered; setRepeat() changes
the layout, the size or the arrangement, and on('repeat') reports every
change with { repeat }, a minSize that moved included:
designer.on('repeat', ({ repeat }) => { sizeSlider.min = repeat.minSize; sizeSlider.value = Math.max(repeat.size, repeat.minSize); shuffleButton.hidden = repeat.mode !== 'scatter';});designer.setRepeat({ mode: 'scatter', size: 40 });designer.setRepeat({ seed: designer.getRepeat().seed + 1 }); // shuffleA layout that is not offered, a size outside 0 to 100, and turning the repeat
off (mode: null) when the shopper may not, throw a TypeError and change
nothing, as do placements that are not sides, or that leave out one linked
with them. shopperMayChange is for your page to read: the designer does not
hide anything for it. disableRepeat() withdraws the offer and places the
design once again.
approve() and commit() send the choice with the design of every side it is
laid on, and the committed design keeps it; a side it is not laid on is sent
as repeat: null, placed once. A linked group repeats whole: a placement put
back in a group the repeat is laid on repeats with it. The committed design keeps the choice, with the size its tile came to in millimetres. A
repeat the shopper turned off goes as repeat: null, the design placed once,
so a share link that starts on a repeat does not fill its start in. A
tile whose picture would print below the resolution the template asks for is
a warning in the answer’s lowResolution, never a refusal. getResolution(layerId)
measures the uploaded picture at the tile’s size too, as that warning does,
so your page can say a picture may print soft as the shopper drags the size,
without a request.
The template’s background picture fills every tile, so its crop
(setCropMode({ kind: 'background' })) is framed on one tile: the tile at the
centre of the print area. Moving the picture there moves it in every tile, and
the generated form judges the zoom on the tile’s printed size. A scatter
lays its tiles at random places, turns and sizes, with none at a fixed place
to frame, so the background is not cropped while the design scatters:
setCropMode({ kind: 'background' }) returns false, and the generated form
shows the form.crop-scattered message in place of the crop.
Linking sides
Section titled “Linking sides”A garment with several placements need not be designed one side at a time.
linkedPlacements says which of placements one design drives:
placements: [ { placementId: 'front' }, { placementId: 'back' }, { placementId: 'left-sleeve' }, { placementId: 'right-sleeve' },],linkedPlacements: { preset: 'suggested', designSeparately: true },| Field | What it does |
|---|---|
preset |
suggested links the groups the blank suggests: a blank’s placement may name the one it goes with, as a right sleeve names the left, mirrored. all links every placement. each links none. Left out with no groups, suggested. |
groups |
Groups of your own in place of a preset: [[{ placementId: 'front' }, { placementId: 'back', mirror: true }]]. A mirror left out is the blank’s suggestion, else none. |
designSeparately |
Whether a placement may be taken out of its group to be designed on its own. Left out, false. |
Without linkedPlacements every placement is designed on its own, as before.
The placements of a group print one template; a group whose placements name
different templates fails the mount with a TypeError, as do a preset
beside groups, a group of one placement, and a placement named twice or
not among placements.
A group is one side. getSides() lists it once, as the placement shown when
it is one of them, else its first, with every placement it drives and
whether each is mirrored:
[ { placementId: 'front', templateId: 'stripes@2', enabled: true, valid: true, linked: null }, { placementId: 'back', templateId: 'stripes@2', enabled: true, valid: true, linked: null }, { placementId: 'left-sleeve', templateId: 'stripes@2', enabled: true, valid: true, linked: [ { placementId: 'left-sleeve', mirrored: false }, { placementId: 'right-sleeve', mirrored: true }, ], },]Showing any placement of a group, with setSide() or one of its views, edits
them all, and the views of several placements draw each of them with the
design as it is typed. setSideEnabled() takes or leaves out the group as
one. A group is enabled when any of its placements is named enabled, and a
placement put back in its group is enabled with it.
approve() and commit() still make one proof, and one design, per
placement: each is printed at its own print area, so one picture is cropped
to each, and names its group. A mirrored placement’s proof and print are
mirrored left to right, and so is its region in a view of several
placements and its renderSidePrint(). A repeat is laid at one physical scale across the group: its size
is measured on the group’s widest print area, so the pattern is as large on
a narrow sleeve as on a wide one. On a blank whose print areas change with
the size, that is the widest area at the size chosen (setVariant()).
setSideLink() changes a placement’s link while the shopper designs:
designer.setSideLink('right-sleeve', { linked: false }); // design it on its owndesigner.setSideLink('right-sleeve', { linked: true }); // back in its groupdesigner.setSideLink('right-sleeve', { mirrored: false });Taken out, the placement keeps the group’s design as it stood, and is a side
of its own from then on. Put back, the group’s design is the one shown when
the side shown is one of its placements, else the group’s own. Taking a
placement out without designSeparately, and naming one no group links,
throw a TypeError. on('link') reports { placementId, linked, mirrored }.
getState() keeps the links as the shopper changed them, so a mount from
that state as initialValues designs a placement taken out on its own
again, with its own design and mirror.
One picture running unbroken across seams is not this: see Draw across a panel layout.
A sweater with linked sleeves and a pattern
Section titled “A sweater with linked sleeves and a pattern”A sweater with a tab per side, the sleeves as one tab, the pattern’s layout, size and a Shuffle button for a scatter, a button that designs the sleeves separately, and Add to cart:
<div id="sides" role="tablist"></div><div id="canvas"></div><label>Layout <select id="layout"> <option value="grid">Grid</option> <option value="brick">Brick</option> <option value="halfDrop">Half drop</option> <option value="scatter">Scatter</option> </select></label><label>Size <input id="size" type="range" min="1" max="100"></label><button id="shuffle">Shuffle</button><button id="separately">Design the sleeves separately</button><button id="add-to-cart">Add to cart</button>const designer = await Pixonaut.mount('#canvas', { publishableKey, blankId, templateId, placementId: 'front', placements: [ { placementId: 'front' }, { placementId: 'back' }, { placementId: 'left-sleeve' }, { placementId: 'right-sleeve' }, ], linkedPlacements: { preset: 'suggested', designSeparately: true }, showViews: false,});designer.enableRepeat({ size: 30 });
// A tab per side; a linked one names every placement it drives.const tabs = document.querySelector('#sides');const drawTabs = () => { const shown = designer.getState().placementId; tabs.replaceChildren( ...designer.getSides().map((side) => { const placements = side.linked?.map((p) => p.placementId) ?? [side.placementId]; const tab = document.createElement('button'); tab.setAttribute('role', 'tab'); tab.dataset.side = side.placementId; tab.textContent = placements.join(' + '); tab.setAttribute('aria-selected', String(placements.includes(shown))); tab.addEventListener('click', () => designer.setSide(side.placementId)); return tab; }), );};designer.on('side', drawTabs);designer.on('link', drawTabs);drawTabs();
// The pattern: its layout, its size, and another arrangement of a scatter.const layout = document.querySelector('#layout');const size = document.querySelector('#size');const shuffle = document.querySelector('#shuffle');const showRepeat = (repeat) => { layout.value = repeat.mode; size.value = repeat.size; shuffle.hidden = repeat.mode !== 'scatter';};designer.on('repeat', ({ repeat }) => repeat && showRepeat(repeat));showRepeat(designer.getRepeat());layout.addEventListener('change', () => designer.setRepeat({ mode: layout.value }));size.addEventListener('input', () => designer.setRepeat({ size: Number(size.value) }));shuffle.addEventListener('click', () => { designer.setRepeat({ seed: designer.getRepeat().seed + 1 });});
// The right sleeve on its own, and back with the left.const separately = document.querySelector('#separately');separately.addEventListener('click', () => { const apart = separately.getAttribute('aria-pressed') === 'true'; designer.setSideLink('right-sleeve', { linked: apart }); separately.setAttribute('aria-pressed', String(!apart));});
// One design per placement, ordered together as one sweater.document.querySelector('#add-to-cart').addEventListener('click', async () => { const { sides } = await designer.commit(); await addToCart(sides.map(({ placementId, designId }) => ({ placementId, designId })));});addToCart is your storefront’s: your server makes a print of each design
and places one order holding them, as
one item printed on several
placements.
Questions? Write to [email protected].