Add the code in Webflow before </body> or </head>

This code is created for a specific example use case. Feel free to modify the code using ChatGPT to suit your needs.

document.addEventListener("DOMContentLoaded", function () {
  const config = {
    formSelector: '[fs-configurator-element="form"]',
    inputSelector: "[fs-configurator-input]",
    inputAttribute: "fs-configurator-input", // Use this for direct attribute references
    imageSelector: "[fs-configurator-finalimage]",
    imageAttribute: "fs-configurator-finalimage", // Use this for direct attribute references
    sourceSelector: "[fs-configurator-imagesource]",
    sourceAttribute: "fs-configurator-imagesource", // Use this for direct attribute references
    storagePrefix: "user_", // Prefix for local storage keys
  };

  // Updates images based on current selections
  function updateImages() {
    const selections = getSelections();
    const selectionKeys = Object.keys(selections)
      .sort()
      .map((key) => selections[key])
      .join("-")
      .toLowerCase();

    document.querySelectorAll(config.imageSelector).forEach((image) => {
      const imageId = image.getAttribute(config.imageAttribute); // Use attribute from config
      const sourceKey = `${selectionKeys}-${imageId}`; // Dynamically constructs the key to match source
      const matchedSource = findMatchingSource(sourceKey);

      if (matchedSource) {
        image.src = matchedSource;
        console.log(`Image ${imageId} source updated to ${matchedSource}`);
      } else {
        console.log(
          `No matching source found for ${sourceKey}. Image ${imageId} not updated.`,
        );
      }
    });
  }

  // Gathers current selections from form inputs
  function getSelections() {
    const selections = {};
    document.querySelectorAll(config.inputSelector).forEach((input) => {
      if (
        input.checked ||
        input.type === "text" ||
        input.tagName.toLowerCase() === "select"
      ) {
        const attributeValue = input.getAttribute(config.inputAttribute); // Use attribute from config
        selections[attributeValue] = input.value;
      }
    });
    return selections;
  }

  // Finds a matching source based on the constructed key
  function findMatchingSource(sourceKey) {
    const sourceElement = document.querySelector(
      `${config.sourceSelector}[${config.sourceAttribute}="${sourceKey.toLowerCase()}"]`, // Use attribute from config
    );
    return sourceElement ? sourceElement.textContent.trim() : null;
  }

  // Saves the current selections to local storage
  function saveSelections() {
    Object.entries(getSelections()).forEach(([key, value]) => {
      const storageKey = `${config.storagePrefix}${key}`;
      localStorage.setItem(storageKey, value);
      console.log(`Selection saved: ${storageKey} = ${value}`);
    });
  }

  // Loads selections from local storage upon initialization
  function loadSelections() {
    document.querySelectorAll(config.inputSelector).forEach((input) => {
      const key = input.getAttribute(config.inputAttribute); // Use attribute from config
      const storageKey = `${config.storagePrefix}${key}`;
      const value = localStorage.getItem(storageKey);

      if (value !== null) {
        if (input.type === "checkbox" || input.type === "radio") {
          input.checked = input.value === value;
        } else {
          input.value = value;
        }
        console.log(`Loaded selection: ${storageKey} = ${value}`);
      }
    });
  }

  // Attaches change event listeners to form inputs to trigger updates
  document
    .querySelector(config.formSelector)
    .addEventListener("change", function (e) {
      if (e.target.matches(config.inputSelector)) {
        updateImages();
        saveSelections();
      }
    });

  // Initial actions: load selections and update images accordingly
  loadSelections();
  updateImages();
});

Webflow Preview Link

Webflow Live Link

Finsweet Product Configurator