feeeedback
Version:
Simple, beautiful, open-source feedback widget with Discord webhooks
141 lines (122 loc) • 4.29 kB
JavaScript
(function () {
const FRAME_WIDTH = 320;
const FRAME_HEIGHT = 178;
const MARGIN = 4;
const ANIMATION_DURATION = 200;
const frameUrl =
"https://feeeedback.tiag.workers.dev/widget/index.html#" +
encodeURIComponent(
location.hostname + "/" + (document.currentScript?.getAttribute("pid") || document.querySelector("script[pid]")?.getAttribute("pid"))
);
const frame = document.createElement("iframe");
frame.src = frameUrl;
frame.style.display = "none";
frame.style.width = FRAME_WIDTH + "px";
frame.style.height = FRAME_HEIGHT + "px";
frame.style.border = "none";
frame.style.boxShadow = "0 18px 50px -10px rgba(0, 0, 0, 0.2)";
frame.style.borderRadius = "16px";
frame.frameborder = "none";
frame.style.position = "fixed";
frame.style.backgroundColor = "white";
frame.style.zIndex = "9999";
frame.style.transition = `opacity ${ANIMATION_DURATION}ms ease, transform ${ANIMATION_DURATION}ms ease`;
frame.style.opacity = "0";
frame.style.transform = "scale(0.95)";
document.body.appendChild(frame);
function calculatePosition(triggerElement) {
const viewport = {
width: window.innerWidth,
height: window.innerHeight,
};
const trigger = {
top: triggerElement.getBoundingClientRect().top,
left: triggerElement.getBoundingClientRect().left,
width: triggerElement.offsetWidth,
height: triggerElement.offsetHeight,
bottom: triggerElement.getBoundingClientRect().bottom,
right: triggerElement.getBoundingClientRect().right,
};
const position = {
top: 0,
left: 0,
placement: "bottom",
};
if (trigger.bottom + FRAME_HEIGHT + MARGIN <= viewport.height) {
position.top = trigger.bottom + MARGIN;
position.placement = "bottom";
} else if (trigger.top - FRAME_HEIGHT - MARGIN >= 0) {
position.top = trigger.top - FRAME_HEIGHT - MARGIN;
position.placement = "top";
} else {
position.top = Math.max(MARGIN, (viewport.height - FRAME_HEIGHT) / 2);
position.placement = "center";
}
if (trigger.left + FRAME_WIDTH + MARGIN <= viewport.width) {
position.left = trigger.left;
} else if (trigger.right - FRAME_WIDTH >= MARGIN) {
position.left = trigger.right - FRAME_WIDTH;
} else {
position.left = Math.max(MARGIN, (viewport.width - FRAME_WIDTH) / 2);
}
position.top = Math.max(
MARGIN,
Math.min(position.top, viewport.height - FRAME_HEIGHT - MARGIN)
);
position.left = Math.max(
MARGIN,
Math.min(position.left, viewport.width - FRAME_WIDTH - MARGIN)
);
return position;
}
function showFrame(position) {
frame.style.display = "block";
frame.style.top = position.top + "px";
frame.style.left = position.left + "px";
frame.setAttribute("data-placement", position.placement);
requestAnimationFrame(() => {
frame.style.opacity = "1";
frame.style.transform = "scale(1)";
});
}
function hideFrame() {
frame.style.opacity = "0";
frame.style.transform = "scale(0.95)";
setTimeout(() => {
frame.style.display = "none";
}, ANIMATION_DURATION);
}
document.body.addEventListener("click", function (e) {
const element =
e.target.closest("[data-feeeedback]") ||
(e.target.getAttribute("data-feeeedback") ? e.target : null);
if (!element) {
hideFrame();
return;
}
const position = calculatePosition(element);
showFrame(position);
frame.contentWindow.postMessage("feeeedback--reset", "*");
});
window.addEventListener("message", function (e) {
if (e.data == "feeeedback--close") {
hideFrame();
}
});
let resizeTimeout;
window.addEventListener("resize", function () {
if (frame.style.display === "none") return;
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
const element =
document.querySelector("[data-feeeedback]:hover") ||
document.querySelector("[data-feeeedback]");
if (element) {
const position = calculatePosition(element);
frame.style.top = position.top + "px";
frame.style.left = position.left + "px";
frame.setAttribute("data-placement", position.placement);
}
}, 100);
});
})();