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:
2026-07-01 14:26:11 +02:00
parent 6ed4caea4f
commit 762a9e8bca
3 changed files with 112 additions and 3 deletions

View File

@@ -147,15 +147,15 @@
else else
{ {
@* PDF viewer + overlay wrapper *@ @* PDF viewer + overlay wrapper *@
<div id="pdf-editor-wrapper" <div id="pdf-editor-wrapper" class="pdf-editor-wrapper"
style="position: relative; width: 100%; height: 100%; overflow: hidden;"> style="position: relative; width: 100%; height: 100%; overflow: auto;">
@* DxPdfViewer — zoom fixed to 1.0 for reliable coordinate mapping *@ @* DxPdfViewer — zoom fixed to 1.0 for reliable coordinate mapping *@
<DxPdfViewer @ref="_pdfViewer" <DxPdfViewer @ref="_pdfViewer"
DocumentContent="@_pdfBytes" DocumentContent="@_pdfBytes"
ZoomLevel="1.0" ZoomLevel="1.0"
IsSinglePagePreview="false" IsSinglePagePreview="false"
CssClass="w-100 h-100" /> CssClass="sender-editor-pdf-viewer" />
@* Transparent overlay for click capture (active only in placement mode) *@ @* Transparent overlay for click capture (active only in placement mode) *@
<div id="pdf-editor-overlay" <div id="pdf-editor-overlay"
@@ -336,6 +336,17 @@
$"[SenderEditor] Total fields: {_signatureFields.Count}"); $"[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 ── // ── Models ──
record SignatureFieldDraft(double XPt, double YPt, int Page, double DisplayX, double DisplayY); record SignatureFieldDraft(double XPt, double YPt, int Page, double DisplayX, double DisplayY);

View File

@@ -51,6 +51,33 @@
overflow: auto; 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 { .pdf-viewer-container {
height: 100%; height: 100%;
display: flex; display: flex;

View File

@@ -1,4 +1,6 @@
window.envelopeEditor = { window.envelopeEditor = {
_overlaySyncState: {},
/** /**
* Returns click coordinates relative to the overlay element. * Returns click coordinates relative to the overlay element.
* @param {string} overlayId - The id of the overlay div * @param {string} overlayId - The id of the overlay div
@@ -17,5 +19,74 @@ window.envelopeEditor = {
containerW: rect.width, containerW: rect.width,
containerH: rect.height 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);
} }
}; };