I’ve run into an issue when trying to animate dynamically injected elements. I’m able to see and edit the element in the Timeline editor, and the animation preview works — but when I click “Save”, the animation doesn’t persist.
The element in question is an info-icon (<span class="aoun-info-circle">
) that gets added to specific Forminator fields via JavaScript on page load. I’m using mp-cursor-tooltip
for Motion.page tooltips, which works perfectly after calling MP.refresh()
.
The tooltips render properly and work great during runtime. But the icon cannot be locked to a timeline: it appears in the builder, animations can be added, but cannot be saved.
What I’ve tried:
Confirmed
mp-cursor-tooltip
is present beforeMP.refresh()
Made sure the icon is in the DOM by the time the Motion.page panel opens
Tried setting fixed IDs or classes (
aoun-info-circle
,mp-anim-target
)Verified that
MP.refresh()
is definitely executedEven with class/ID, animations don’t persist
What I’m asking:
Is there a reliable way to bind animations to dynamically injected elements, and retain them in the Timeline?
Or is there a specific scan/init pattern Motion.page expects for dynamic content to be “storable”?
Would really appreciate your insight — love what you’re building and this is the last piece missing in a perfect use case!
Thanks so much
— Jamal
function insertInfoIcon({ step, type = "checkbox", index = 0, tooltip = "" }) {
const container = document.querySelector(`.forminator-pagination[data-step="${step}"]`);
if (!container) return;
const selector = type === "radio" ? "label.forminator-radio" : "label.forminator-checkbox";
const labels = container.querySelectorAll(selector);
if (labels.length > index) {
const label = labels[index];
if (!label.querySelector('.aoun-info-circle')) {
const info = document.createElement("span");
info.className = "aoun-info-circle";
info.textContent = "i";
info.setAttribute("mp-cursor-tooltip", tooltip);
label.appendChild(info);
}
}
}
function generateIconsFromHTML() {
const tooltipContainer = document.getElementById("tooltip-data");
if (!tooltipContainer) return;
const items = tooltipContainer.querySelectorAll("[data-tooltip]");
items.forEach(item => {
const key = item.getAttribute("data-tooltip");
const text = item.textContent.trim();
const match = key.match(/^step-(\d+)-(checkbox|radio)-(\d+)$/);
if (!match) return;
const [ , step, type, index ] = match;
insertInfoIcon({ step: parseInt(step), type, index: parseInt(index), tooltip: text });
});
}
document.addEventListener("DOMContentLoaded", () => {
let attempts = 0;
const maxAttempts = 30;
const interval = setInterval(() => {
const loaded = document.querySelector('.forminator-pagination[data-step]');
const tooltipReady = document.getElementById('tooltip-data');
if (loaded && tooltipReady) {
clearInterval(interval);
generateIconsFromHTML();
// Re-scan Motion.page tooltips
if (typeof MP !== 'undefined' && typeof MP.refresh === 'function') {
MP.refresh();
}
}
if (++attempts >= maxAttempts) {
clearInterval(interval);
}
}, 300);
});