Enable Blazor-JS communication for PDF viewer zoom

Added `DotNetObjectReference` in `EnvelopeViewer.razor` to enable Blazor-JS communication. Updated `OnAfterRenderAsync` to pass the reference to `pdfViewer.initialize`. Introduced `[JSInvokable]` method `OnZoomChanged` to handle zoom updates from JavaScript.

Enhanced `DisposeAsync` to clean up resources, including disposing of the `.NET reference` and invoking a JavaScript `dispose` method.

In `pdf-viewer.js`, modified `initialize` to accept a `.NET reference` and added `attachWheelEvent` to handle zooming via mouse wheel with `Ctrl`/`Meta` key. Updated `dispose` to clean up event listeners and reset the `.NET reference`. Added `getScale` to retrieve the current zoom scale.

Improved resource cleanup and event handling to prevent memory leaks and ensure stability.
This commit is contained in:
2026-06-05 12:36:59 +02:00
parent 3539907054
commit 41f3df4c71
2 changed files with 76 additions and 12 deletions

View File

@@ -117,6 +117,7 @@
int _currentPage = 1;
int _totalPages = 0;
int _currentZoom = 150;
DotNetObjectReference<EnvelopeViewer>? _dotNetRef;
protected override async Task OnInitializedAsync() {
if (string.IsNullOrWhiteSpace(EnvelopeKey)) {
@@ -147,7 +148,8 @@
await Task.Delay(500);
try {
var success = await JSRuntime.InvokeAsync<bool>("pdfViewer.initialize", "pdf-canvas", _pdfDataUrl);
_dotNetRef = DotNetObjectReference.Create(this);
var success = await JSRuntime.InvokeAsync<bool>("pdfViewer.initialize", "pdf-canvas", _pdfDataUrl, _dotNetRef);
if (success) {
_pdfLoaded = true;
@@ -162,6 +164,13 @@
}
}
[JSInvokable]
public async Task OnZoomChanged(double scale)
{
_currentZoom = (int)(scale * 100);
await InvokeAsync(StateHasChanged);
}
async Task NextPage() {
if (await JSRuntime.InvokeAsync<bool>("pdfViewer.nextPage")) {
_currentPage = await JSRuntime.InvokeAsync<int>("pdfViewer.getCurrentPage");
@@ -187,6 +196,13 @@
}
public async ValueTask DisposeAsync() {
// Cleanup if needed
if (_pdfLoaded) {
try {
await JSRuntime.InvokeVoidAsync("pdfViewer.dispose");
} catch {
// Ignore errors during disposal
}
}
_dotNetRef?.Dispose();
}
}

View File

@@ -1,19 +1,24 @@
// PDF.js Viewer for Blazor WASM
window.pdfViewer = {
pdfDoc: null,
pageNum: 1,
pageRendering: false,
pageNumPending: null,
scale: 1.5,
canvas: null,
ctx: null,
totalPages: 0,
currentRenderTask: null,
pdfDoc: null,
pageNum: 1,
pageRendering: false,
pageNumPending: null,
scale: 1.5,
canvas: null,
ctx: null,
totalPages: 0,
currentRenderTask: null,
dotNetReference: null,
wheelEventAttached: false,
async initialize(canvasId, pdfDataUrl) {
async initialize(canvasId, pdfDataUrl, dotNetRef) {
try {
console.log('PDF.js initialization started for canvas:', canvasId);
// Store .NET reference for callbacks
this.dotNetReference = dotNetRef;
// Wait for PDF.js to load
if (typeof window.pdfjsLib === 'undefined') {
console.error('PDF.js library not loaded, waiting...');
@@ -33,6 +38,9 @@ currentRenderTask: null,
this.ctx = this.canvas.getContext('2d');
// Attach mouse wheel event listener
this.attachWheelEvent();
// Load PDF from data URL
const uint8Array = this.base64ToUint8Array(pdfDataUrl);
console.log('PDF data converted to Uint8Array, size:', uint8Array.length);
@@ -52,6 +60,35 @@ currentRenderTask: null,
}
},
attachWheelEvent() {
if (this.wheelEventAttached) return;
// Attach to the entire document body for global zoom control
document.body.addEventListener('wheel', (e) => {
// Check if Ctrl key is pressed (zoom gesture)
if (e.ctrlKey || e.metaKey) {
e.preventDefault();
if (e.deltaY < 0) {
// Scroll up = Zoom In
this.zoomIn();
if (this.dotNetReference) {
this.dotNetReference.invokeMethodAsync('OnZoomChanged', this.scale);
}
} else {
// Scroll down = Zoom Out
this.zoomOut();
if (this.dotNetReference) {
this.dotNetReference.invokeMethodAsync('OnZoomChanged', this.scale);
}
}
}
}, { passive: false });
this.wheelEventAttached = true;
console.log('Wheel event listener attached to document body');
},
async waitForPdfJs() {
for (let i = 0; i < 50; i++) {
if (typeof window.pdfjsLib !== 'undefined') {
@@ -176,6 +213,17 @@ currentRenderTask: null,
getScale() {
return this.scale;
},
dispose() {
// Clean up event listeners
if (this.wheelEventAttached) {
// Note: We can't remove the exact listener without keeping a reference
// but we can at least mark it as disposed
this.wheelEventAttached = false;
this.dotNetReference = null;
console.log('PDF viewer disposed');
}
}
};