Improve PDF viewer overlay synchronization
Refactor `EnvelopeSenderEditorPage.razor` to enhance the structure and behavior of the PDF editor wrapper: - Add `class="pdf-editor-wrapper"` and update `overflow` to `auto`. - Update `DxPdfViewer`'s `CssClass` to `sender-editor-pdf-viewer`. - Introduce `OnAfterRenderAsync` to synchronize the overlay with the viewer. Add new styles in `envelope-viewer.css` for better layout: - Ensure `.pdf-editor-wrapper` and `.sender-editor-pdf-viewer` occupy full dimensions. - Center and align content within the PDF viewer. Enhance `envelope-editor.js` with `syncOverlayToPage`: - Dynamically adjust overlay position and size relative to the viewer. - Use `MutationObserver` and event listeners for real-time synchronization. - Handle delayed rendering with scheduled sync attempts. These changes improve overlay alignment, user experience, and code maintainability.
This commit is contained in:
@@ -147,15 +147,15 @@
|
||||
else
|
||||
{
|
||||
@* PDF viewer + overlay wrapper *@
|
||||
<div id="pdf-editor-wrapper"
|
||||
style="position: relative; width: 100%; height: 100%; overflow: hidden;">
|
||||
<div id="pdf-editor-wrapper" class="pdf-editor-wrapper"
|
||||
style="position: relative; width: 100%; height: 100%; overflow: auto;">
|
||||
|
||||
@* DxPdfViewer — zoom fixed to 1.0 for reliable coordinate mapping *@
|
||||
<DxPdfViewer @ref="_pdfViewer"
|
||||
DocumentContent="@_pdfBytes"
|
||||
ZoomLevel="1.0"
|
||||
IsSinglePagePreview="false"
|
||||
CssClass="w-100 h-100" />
|
||||
CssClass="sender-editor-pdf-viewer" />
|
||||
|
||||
@* Transparent overlay for click capture (active only in placement mode) *@
|
||||
<div id="pdf-editor-overlay"
|
||||
@@ -336,6 +336,17 @@
|
||||
$"[SenderEditor] Total fields: {_signatureFields.Count}");
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (!_pdfLoaded || _errorMessage is not null)
|
||||
return;
|
||||
|
||||
await JSRuntime.InvokeVoidAsync(
|
||||
"envelopeEditor.syncOverlayToPage",
|
||||
"pdf-editor-wrapper",
|
||||
"pdf-editor-overlay");
|
||||
}
|
||||
|
||||
// ── Models ──
|
||||
record SignatureFieldDraft(double XPt, double YPt, int Page, double DisplayX, double DisplayY);
|
||||
|
||||
|
||||
@@ -51,6 +51,33 @@
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.pdf-editor-wrapper {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.sender-editor-pdf-viewer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.sender-editor-pdf-viewer .dxbrv-document-surface {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sender-editor-pdf-viewer .dxbrv-report-preview-content-flex-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sender-editor-pdf-viewer .dxbrv-report-preview-content {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.pdf-viewer-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
window.envelopeEditor = {
|
||||
_overlaySyncState: {},
|
||||
|
||||
/**
|
||||
* Returns click coordinates relative to the overlay element.
|
||||
* @param {string} overlayId - The id of the overlay div
|
||||
@@ -17,5 +19,74 @@ window.envelopeEditor = {
|
||||
containerW: rect.width,
|
||||
containerH: rect.height
|
||||
};
|
||||
},
|
||||
|
||||
syncOverlayToPage: function (wrapperId, overlayId) {
|
||||
const wrapper = document.getElementById(wrapperId);
|
||||
const overlay = document.getElementById(overlayId);
|
||||
|
||||
if (!wrapper || !overlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existing = window.envelopeEditor._overlaySyncState[overlayId];
|
||||
if (existing) {
|
||||
return existing.sync();
|
||||
}
|
||||
|
||||
const findTarget = (currentWrapper) => {
|
||||
const page = currentWrapper.querySelector(".dxbrv-report-preview-content");
|
||||
if (page) {
|
||||
return page;
|
||||
}
|
||||
|
||||
return currentWrapper.querySelector(".dxbrv-report-preview-content-img") ||
|
||||
currentWrapper.querySelector("img.dxbrv-report-preview-content-img") ||
|
||||
currentWrapper.querySelector(".dxbrv-document-surface img");
|
||||
};
|
||||
|
||||
const sync = () => {
|
||||
const currentWrapper = document.getElementById(wrapperId);
|
||||
const currentOverlay = document.getElementById(overlayId);
|
||||
|
||||
if (!currentWrapper || !currentOverlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = findTarget(currentWrapper);
|
||||
if (!target) {
|
||||
currentOverlay.style.left = "0px";
|
||||
currentOverlay.style.top = "0px";
|
||||
currentOverlay.style.width = "0px";
|
||||
currentOverlay.style.height = "0px";
|
||||
return;
|
||||
}
|
||||
|
||||
const wrapperRect = currentWrapper.getBoundingClientRect();
|
||||
const targetRect = target.getBoundingClientRect();
|
||||
|
||||
currentOverlay.style.left = `${targetRect.left - wrapperRect.left + currentWrapper.scrollLeft}px`;
|
||||
currentOverlay.style.top = `${targetRect.top - wrapperRect.top + currentWrapper.scrollTop}px`;
|
||||
currentOverlay.style.width = `${targetRect.width}px`;
|
||||
currentOverlay.style.height = `${targetRect.height}px`;
|
||||
};
|
||||
|
||||
const scheduleSync = () => requestAnimationFrame(sync);
|
||||
|
||||
const observer = new MutationObserver(scheduleSync);
|
||||
observer.observe(wrapper, { childList: true, subtree: true, attributes: true });
|
||||
|
||||
wrapper.addEventListener("scroll", scheduleSync, { passive: true });
|
||||
window.addEventListener("resize", scheduleSync);
|
||||
|
||||
window.envelopeEditor._overlaySyncState[overlayId] = {
|
||||
sync,
|
||||
observer
|
||||
};
|
||||
|
||||
sync();
|
||||
setTimeout(sync, 50);
|
||||
setTimeout(sync, 150);
|
||||
setTimeout(sync, 400);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user