Skip to content

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.

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 }); // shuffle

A 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.

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 own
designer.setSideLink('right-sleeve', { linked: true }); // back in its group
designer.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].

Status · Terms of service · Privacy policy · Subprocessors