By default, Tweakwise Visuals serve a single image URL as defined in your data feed. Tweakwise does not provide an out-of-the-box convention or setting to display alternative images on different breakpoints. Achieving responsiveness requires a custom implementation using the Tweakwise JS render hook or your own frontend logic.
Implementing responsive support allows you to serve optimized image variants based on the user's viewport, improving mobile performance and preventing layout shifts. This guide explains how to programmatically swap visual assets within the Tweakwise JS implementation.
Implementation
To make visuals responsive, you must hook into the default rendering logic in the Tweakwise config on your front-end.
Step 1: Prepare your data in Tweakwise
Because there is no default "mobile image" field, you must add custom attributes to your Visuals in the Tweakwise app (or your feed). For example, add an attribute mobile_image_url to your visual.
Next, extend the API reponse with the attribute to expose the attribute so you can use it in Tweakwise JS.
<item>
<id>promo-2024-04-perfect-fit</id>
<name>Find your perfect fit</name>
<image><![CDATA[https://cdn.tweakwise.com/content/shop/findyourperfectfit.png]]></image>
...
<attributes>
...
<attribute>
<name>mobile_image_url</name>
<value><![CDATA[https://cdn.tweakwise.com/content/shop/findyourperfectfit_mobile.png]]></value>
</attribute>
...
</attributes>
</item>Step 2: Use the visibility hook
Add the following logic to your window["twn-starter-config"]. This listens for the item visibility event, identifies visual items, and appends the mobile image to the tile link.
If you want to display different images on mobile and desktop, provide alternative images in the item attributes:
Then follow these steps:
- Extend API reponse with the attribute
- Use the
twn.product.visiblehook to create and add the image:
window["twn-starter-config"].on = {
"twn.product.visible": function (event) {
const item = event.data;
if (item.type !== "visual") return;
const mobileAttr = item.attributes.find((attr) => attr.name === "mobile_image_url");
const mobileUrl = mobileAttr ? mobileAttr.values[0] : null;
if (mobileUrl) {
const element = createElement("img");
element.src = mobileUrl;
element.classList.add("tw__visual-mobile", "is-visible-mobile");
// Append to the standard Tweakwise tile link
const target = event.targetElement.querySelector(".twn-content-tile__link");
if (target && !target.querySelector(".tw__visual-mobile")) {
target.append(element);
}
}
}
};Step 3: Add basic styling
Since both the desktop and mobile images now exist in the DOM, use CSS to toggle visibility based on your project's breakpoints.
/* Hide mobile image by default */
.tw__visual-mobile {
display: none;
}
@media (max-width: 768px) {
/* Hide the default desktop image */
.twn-content-tile__link > img:not(.tw__visual-mobile) {
display: none;
}
/* Show the injected mobile image */
.tw__visual-mobile.is-visible-mobile {
display: block;
width: 100%;
height: auto;
}
}
Notes & Important Considerations
- DOM Duplication: Ensure you check if the mobile image already exists (!target.querySelector(".tw__visual-mobile")) before appending to prevent multiple images from being added during re-renders.
- Attribute Naming: The code relies on the internal name
image_mobile. If you named your attribute differently in the Tweakwise Hub, update the .find() logic accordingly. - Performance: The twn.product.visible hook fires when items enter the viewport. This is efficient as it lazily handles the DOM manipulation only for visible elements.
FAQ
Why use the visibility hook instead of a standard render function?
The visibility hook allows you to change the existing Tweakwise JS generated HTML without rewriting the entire rendering logic. It is particularly useful when you want to keep the default tile structure but inject additional responsive layers.
Will this cause a flash of unstyled content (FOUC)?
If the CSS is loaded correctly, the desktop image will be hidden and the mobile image shown instantaneously. To further optimize, ensure your CSS is included in the head of your document.
How do I handle visuals that don't have a mobile image set?
The implementation includes a check (if (mobileUrl)). If no mobile image is found in the attributes, the script will skip the injection, and the browser will fallback to displaying the default desktop image.
