Direct Code Integration¶
Use this method if you want to integrate widgets directly into any website, or need more control over placement and behavior than the admin editor offers.
Prerequisites¶
- Partner ID and Catalog ID — obtain these from the Glance AI team
- Active Glance AI Account — your account status must be active
- Glance AI Application — must be installed on your store
Replace the placeholders before going live
The snippet below contains placeholder values like YOUR_STORE_ID_HERE and YOUR_CATALOG_ID_HERE. Replace all of them with your real credentials and page details before testing or publishing — the widget won't load with placeholders in place.
Implementation¶
Copy the code snippet below and customize the configuration:
<div id="glance-widget-container" class="widget-shimmer"></div>
<style>
#glance-widget-container {
display: block;
}
/* Shimmer effect */
.widget-shimmer {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 8px;
height: 100%;
}
@keyframes shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}
</style>
<script>
(function () {
try {
// ===== CONFIGURATION - CUSTOMIZE THESE VALUES =====
const CONFIG = {
// Required: Your Glance AI credentials
// these creds will be on APP metafields for shopify
//partnerId: app.metafields.glance_ai.store_id,
//catalogId: app.metafields.glance_ai.catalog_id,
partnerId: "YOUR_STORE_ID_HERE",
catalogId: "YOUR_CATALOG_ID_HERE",
// Required: Widget configuration
widgetType: "try_on_banner", // Options: 'chat', 'try_on_banner', 'try_on_button', 'search'
// Required: Page information
pageType: "PDP", // Options: 'PDP' (Product), 'PLP' (Collection), 'HOME', 'OTHER'
pageIdentifier: "YOUR_PRODUCT_OR_PAGE_ID", // Product ID, Collection ID, or unique page identifier
storeHandle: "your-store-handle",
// Optional: Theme customization
theme: "purple", // Options: 'purple', 'white'
cornerStyle: "rounded", // Options: 'rounded', 'square'
fontStyle: "montserrat", // Options: 'montserrat', 'ttcommons'
// Optional: Widget customization
brandLogo: "", // URL to your brand logo
title: "How will this dress look on me?", // For banner/chat widgets
subtitle: "Upload your photo and virtually try it on", // For banner/chat widgets
backgroundColor:
"linear-gradient(97.16deg, rgba(157, 53, 255, 0.3) 35.24%, rgba(1, 25, 244, 0.3) 67.13%, rgba(58, 158, 174, 0.3) 99.02%), linear-gradient(0deg, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.8))", // For banner/chat widgets
// Optional: Button positioning (for try_on_button type)
buttonPosition: "absolute", // 'absolute' or 'relative'
buttonPlacement: "right", // 'left' or 'right'
buttonBottom: 20, // Bottom position in pixels
buttonSide: 20, // Side position in pixels (left or right based on buttonPlacement)
// Optional: Custom styling
customCSS: "", // Custom CSS to apply to widget container
// Optional: Animation settings
productIndex: 0, // Product position in collection (for try-on widgets)
animateTryOnButton: true, // Whether to animate try-on button
};
// ===== END CONFIGURATION =====
const containerId = "glance-widget-container";
const IFRAME_ORIGIN = "https://embed.glance.com";
let isInitialized = false;
// Function to check if conditions are met
function shouldInitializeWidget() {
return window.innerWidth < 768; // Mobile only
}
// Initialize Intersection Observer
function setupIntersectionObserver() {
const widgetContainer = document.getElementById(containerId);
if (!widgetContainer) {
console.warn("Glance AI Widget: Container not found:", containerId);
return;
}
// Create intersection observer
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && !isInitialized) {
console.log(
"Glance AI Widget: Container in viewport, initializing...",
);
isInitialized = true;
initializeGlanceAIWidget();
observer.disconnect();
}
});
},
{
threshold: 0.1,
rootMargin: "50px",
},
);
observer.observe(widgetContainer);
console.log("Glance AI Widget: Intersection Observer setup complete");
}
function initializeGlanceAIWidget() {
// Add widget-specific styles
const style = document.createElement("style");
style.textContent = `
#glance-widget-iframe {
width: 100%;
height: 100%;
border: none;
background: transparent;
z-index: 10;
}
/* Widget type specific styles */
#glance-widget-iframe.glance-try_on_button {
width: 92px !important;
height: 0px;
}
#glance-widget-iframe.glance-try_on_banner {
width: 100% !important;
height: 0px;
}
#glance-widget-iframe.glance-chat {
width: 100% !important;
height: 0px;
}
`;
document.head.appendChild(style);
const widgetContainer = document.getElementById(containerId);
// Apply custom CSS if provided
if (CONFIG.customCSS && widgetContainer) {
widgetContainer.style.cssText += CONFIG.customCSS;
}
// Set parent element to position relative
if (widgetContainer && widgetContainer.parentElement) {
widgetContainer.parentElement.style.position = "relative";
}
// Apply positioning styles for try_on_button widget type
if (CONFIG.widgetType === "try_on_button" && widgetContainer) {
widgetContainer.style.position = CONFIG.buttonPosition;
widgetContainer.style.bottom = CONFIG.buttonBottom + "px";
// Apply side positioning based on buttonPlacement
if (CONFIG.buttonPlacement === "left") {
widgetContainer.style.left = CONFIG.buttonSide + "px";
} else {
widgetContainer.style.right = CONFIG.buttonSide + "px";
}
}
// Create iframe element for widget
const widgetIframe = document.createElement("iframe");
widgetIframe.id = "glance-widget-iframe";
widgetIframe.className = `glance-${CONFIG.widgetType}`;
// Remove shimmer effect when iframe is loaded
widgetIframe.addEventListener("load", function () {
if (widgetContainer) {
widgetContainer.classList.remove("widget-shimmer");
}
});
// Append iframe to container
if (widgetContainer) {
widgetContainer.appendChild(widgetIframe);
}
// Cookie management functions
function setCookie(name, value, expiryDays) {
const date = new Date();
date.setTime(date.getTime() + expiryDays * 24 * 60 * 60 * 1000);
const expires = `expires=${date.toUTCString()}`;
document.cookie = `${name}=${value};${expires};path=/;SameSite=Strict;Secure`;
}
function getCookie(name) {
const nameEQ = name + "=";
const ca = document.cookie.split(";");
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) === " ") c = c.substring(1, c.length);
if (c.indexOf(nameEQ) === 0)
return c.substring(nameEQ.length, c.length);
}
return null;
}
function deleteCookie(name) {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
}
function getCurrentCookieData() {
const cookies = {};
const authTokens = ["anonAccessToken", "accessToken", "refreshToken"];
authTokens.forEach((tokenName) => {
const value = getCookie(tokenName);
if (value) {
cookies[tokenName] = value;
}
});
return cookies;
}
// Handle iframe size updates
function handleSizeUpdateRequest(data) {
if (
data.widgetType !== CONFIG.widgetType ||
data.containerId !== containerId
) {
return;
}
console.log("Widget received size update request:", data);
if (widgetIframe) {
widgetIframe.className =
widgetIframe.className
.split(" ")
.filter((cls) => !cls.startsWith("glance-"))
.join(" ") + ` glance-${CONFIG.widgetType}`;
// Set dynamic height if provided
if (!isNaN(data.height)) {
widgetIframe.style.height = data.height + "px";
}
}
}
function getWidgetIdentifier() {
if (
CONFIG.widgetType === "try_on_button" ||
CONFIG.widgetType === "try_on_banner"
) {
return CONFIG.pageIdentifier; // Should be variant/product ID for try-on widgets
} else if (CONFIG.widgetType === "chat") {
return CONFIG.pageIdentifier; // Can be any page identifier for chat
}
}
// Initialize widget iframe
function initializeWidget() {
if (!widgetIframe) return;
const widgetUrl = `${IFRAME_ORIGIN}/build/es/latest/widgets/widget.html?widgetType=${CONFIG.widgetType}`;
widgetIframe.src = widgetUrl;
console.log("Widget iframe initialized:", widgetUrl);
}
// Send initialization data to widget
function sendInitializationData() {
if (widgetIframe && widgetIframe.contentWindow) {
const cookieData = getCurrentCookieData();
const initData = {
partnerId: CONFIG.partnerId,
catalogId: CONFIG.catalogId,
theme: CONFIG.theme,
cornerStyle: CONFIG.cornerStyle,
fontStyle: CONFIG.fontStyle,
storeHandle: CONFIG.storeHandle,
brandLogo: CONFIG.brandLogo,
pageUrl: window.location.href,
pageType: CONFIG.pageType,
pageIdentifier: CONFIG.pageIdentifier,
cookieData: cookieData,
widgetData: {
widgetType: CONFIG.widgetType,
widgetIdentifier: getWidgetIdentifier(),
containerId: containerId,
productIndex: CONFIG.productIndex,
animateTryOnButton: CONFIG.animateTryOnButton,
buttonPlacement: CONFIG.buttonPlacement,
title: CONFIG.title,
subtitle: CONFIG.subtitle,
backgroundColor: CONFIG.backgroundColor,
},
};
widgetIframe.contentWindow.postMessage(
{
type: "INIT_DATA_RESPONSE",
data: initData,
},
IFRAME_ORIGIN,
);
console.log("Sent initialization data to widget:", initData);
}
}
function hideWidget(data) {
if (
data.widgetType === CONFIG.widgetType &&
data.containerId === containerId
) {
const widgetContainer = document.getElementById(containerId);
if (widgetContainer) {
widgetContainer.innerHTML = "";
console.log("Widget hidden and content cleared");
}
}
}
// Event listeners for iframe communication
window.addEventListener("message", (event) => {
if (event.origin !== IFRAME_ORIGIN) return;
const { type, data } = event.data;
switch (type) {
case "REQUEST_INIT_DATA":
console.log("Widget received REQUEST_INIT_DATA");
sendInitializationData();
break;
case "WIDGET_SIZE_UPDATE_REQUEST":
handleSizeUpdateRequest(data);
break;
case "COOKIE_SET_REQUEST":
const { name, value, expiryDays } = data;
setCookie(name, value, expiryDays);
break;
case "COOKIE_DELETE_REQUEST":
deleteCookie(data.name);
break;
case "HIDE_WIDGET":
hideWidget(data);
break;
default:
console.log("Widget received unknown message:", type);
break;
}
});
// Initialize the widget
initializeWidget();
}
// Setup intersection observer when DOM is ready
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
if (shouldInitializeWidget()) {
setupIntersectionObserver();
}
});
} else {
if (shouldInitializeWidget()) {
setupIntersectionObserver();
}
}
} catch (error) {
console.error("Glance AI Widget: Initialization error:", error);
}
})();
</script>
Mobile-only by design
The snippet initializes the widget only when the viewport is under 768px wide (shouldInitializeWidget). It also waits until the container scrolls into view before loading, keeping your page fast. To test, use your browser's device toolbar or a real mobile device.