Experiment Issues

Launch Experiments Using Custom JavaScript When Elements Appear in DOM

Trigger Convert experiments dynamically when elements become visible on screen using custom JavaScript for efficient and optimized A/B testing.

Overview

This guide explains how to trigger Convert experiments when specific elements become visible on the user's screen (viewport). Think of the viewport as a window frame—only elements that appear within this frame are visible to your users. This solution is particularly useful when:

  • You don't have access to the Element In-view trigger feature in your current plan.

  • You need to trigger experiments when elements actually become visible on the user's screen.

  • You want to ensure experiments only run when specific content enters the user's screen view.

  • You need to trigger experiments based on users scrolling to specific content.

Implementation

Step 1: Access Global JavaScript

  1. Navigate to your Convert dashboard.

  2. Go to Your Project > Configuration > Global JS.

  3. Add the following code to the Global JS section.

Step 2: Add the Code

// Configuration variables - MODIFY THESE
const SELECTOR_TO_WAIT_FOR = '.my-element';  // Change to your target element's selector
const EXPERIMENT_ID = '123456789';           // Change to your experiment ID

function waitForElementInViewport(selector, experimentId) {
    return new Promise((resolve, reject) => {
        let elementFound = false;
        let elementInViewport = false;

        // Set a timeout to reject the promise
        const timeoutId = setTimeout(() => {
            cleanup();
            reject(new Error(`Timeout waiting for element in viewport: ${selector}`));
        }, 5000);

        function cleanup() {
            if (intersectionObserver) intersectionObserver.disconnect();
            if (domObserver) domObserver.disconnect();
            clearTimeout(timeoutId);
        }

        // Create Intersection Observer with minimum threshold
        const intersectionObserver = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                elementInViewport = entry.isIntersecting;
                
                // Only launch if element is currently in viewport when found
                if (elementFound && elementInViewport) {
                    cleanup();
                    launchExperiment(experimentId);
                    resolve(entry.target);
                }
            });
        },{
            root: null,
            threshold: 0 // trigger as soon as even 1px is visible
        });

        // Create DOM observer
        const domObserver = new MutationObserver(() => {
            const element = document.querySelector(selector);
            if (element && !elementFound) {
                elementFound = true;
                intersectionObserver.observe(element);

                // If element is immediately in viewport when found
                const rect = element.getBoundingClientRect();
                if (rect.top < window.innerHeight && rect.bottom > 0) {
                    elementInViewport = true;
                    cleanup();
                    launchExperiment(experimentId);
                    resolve(element);
                }
            }
        });

        // Check if element already exists
        const element = document.querySelector(selector);
        if (element) {
            elementFound = true;
            intersectionObserver.observe(element);

            // Check if already in viewport
            const rect = element.getBoundingClientRect();
            if (rect.top < window.innerHeight && rect.bottom > 0) {
                elementInViewport = true;
                cleanup();
                launchExperiment(experimentId);
                resolve(element);
                return;
            }
        }

        // Start observing DOM changes
        domObserver.observe(document.documentElement, {
            childList: true,
            subtree: true
        });
    });
}

function launchExperiment(experimentId) {
    // Set the experiment flag
    window[`experiment${experimentId}flag`] = true;

    // Initialize converter queue if it doesn't exist
    window._conv_q = window._conv_q || [];

    // Push experiment execution
    window._conv_q.push({
        what: "executeExperiment",
        params: {
            experienceId: experimentId,
            triggerIntegrations: false
        }
    });
}

// Execute the code
waitForElementInViewport(SELECTOR_TO_WAIT_FOR, EXPERIMENT_ID)
    .then(element => {
        console.log('Element found in viewport and experiment launched:', element);
        console.log('Experiment flag set:', window[`experiment${EXPERIMENT_ID}flag`]);
    })
    .catch(error => {
        console.error('Error:', error);
    });

Step 3: Configure the Code

  • Change SELECTOR_TO_WAIT_FOR to match your target element's CSS selector.

  • Update EXPERIMENT_ID with your specific experiment ID.

How It Works

This code:

  • Watches for a specific element to appear in the DOM.

  • Sets a flag variable (experiment{ID}flag) when the element is found.

  • Launches the Convert experiment only after the element appears.

  • Uses MutationObserver for efficient DOM monitoring.

  • Includes a 5-second timeout to prevent infinite waiting.

Key Features

  • Resource Efficient: Uses MutationObserver instead of polling.

  • Automatic Cleanup: Properly disconnects observers when done.

  • Error Handling: Includes timeout and error catching.

  • Debug Logging: Console logs for troubleshooting.

  • Experiment Flag: Sets a trackable flag before launching.

When to Use This Solution

This code is ideal when:

  • Elements load dynamically after the initial page load.

  • You need to ensure experiments only run after specific content is available.

  • You want to track when experiments are actually ready to run.

  • You need a reliable way to trigger experiments based on DOM changes.

Best Practices

Selector Choice

  • Use specific, unique selectors.

  • Avoid overly complex selectors.

  • Test your selector in the browser console first.

Timeout Setting

  • The default timeout is 5 seconds.

  • Adjust based on your page's typical load time.

  • Consider increasing for slower-loading pages.

Error Monitoring

  • Check browser console for error messages.

  • Monitor the experiment flag status.

  • Test thoroughly in different scenarios.

Troubleshooting

If the experiment isn't triggering:

  1. Verify your selector in the browser console.

  2. Check if the experiment ID is correct.

  3. Look for error messages in the console.

  4. Ensure the element actually appears in the DOM.

  5. Consider increasing the timeout value.

Additional Notes

  • This code should be added to the Global JS section to ensure it's available across all pages.

  • The solution is particularly useful for single-page applications (SPAs) or sites with dynamic content.

  • Multiple instances can be used for different experiments by duplicating and modifying the configuration variables.