-
-
Save larsneo/bb75616e9426ae589f50e8c8411020f6 to your computer and use it in GitHub Desktop.
<!-- Goes into viewer.html just before ending </body> --> | |
<script> | |
let pinchZoomEnabled = false; | |
function enablePinchZoom(pdfViewer) { | |
let startX = 0, startY = 0; | |
let initialPinchDistance = 0; | |
let pinchScale = 1; | |
const viewer = document.getElementById("viewer"); | |
const container = document.getElementById("viewerContainer"); | |
const reset = () => { startX = startY = initialPinchDistance = 0; pinchScale = 1; }; | |
// Prevent native iOS page zoom | |
//document.addEventListener("touchmove", (e) => { if (e.scale !== 1) { e.preventDefault(); } }, { passive: false }); | |
document.addEventListener("touchstart", (e) => { | |
if (e.touches.length > 1) { | |
startX = (e.touches[0].pageX + e.touches[1].pageX) / 2; | |
startY = (e.touches[0].pageY + e.touches[1].pageY) / 2; | |
initialPinchDistance = Math.hypot((e.touches[1].pageX - e.touches[0].pageX), (e.touches[1].pageY - e.touches[0].pageY)); | |
} else { | |
initialPinchDistance = 0; | |
} | |
}); | |
document.addEventListener("touchmove", (e) => { | |
if (initialPinchDistance <= 0 || e.touches.length < 2) { return; } | |
if (e.scale !== 1) { e.preventDefault(); } | |
const pinchDistance = Math.hypot((e.touches[1].pageX - e.touches[0].pageX), (e.touches[1].pageY - e.touches[0].pageY)); | |
const originX = startX + container.scrollLeft; | |
const originY = startY + container.scrollTop; | |
pinchScale = pinchDistance / initialPinchDistance; | |
viewer.style.transform = `scale(${pinchScale})`; | |
viewer.style.transformOrigin = `${originX}px ${originY}px`; | |
}, { passive: false }); | |
document.addEventListener("touchend", (e) => { | |
if (initialPinchDistance <= 0) { return; } | |
viewer.style.transform = `none`; | |
viewer.style.transformOrigin = `unset`; | |
PDFViewerApplication.pdfViewer.currentScale *= pinchScale; | |
const rect = container.getBoundingClientRect(); | |
const dx = startX - rect.left; | |
const dy = startY - rect.top; | |
container.scrollLeft += dx * (pinchScale - 1); | |
container.scrollTop += dy * (pinchScale - 1); | |
reset(); | |
}); | |
} | |
document.addEventListener('webviewerloaded', () => { | |
if (!pinchZoomEnabled) { | |
pinchZoomEnabled = true; | |
enablePinchZoom(); | |
} | |
}); | |
</script> |
I've tested this and the ES6 version doesn't seem to trigger the event webviewerloaded
.
I had to do this janky stuff:
if('ontouchstart' in document)
{
pinchZoomEnabled = true;
enablePinchZoom();
}
Since I'm using PDF.js as a fallback for mobile devices, this isn't a problem for me.
The way you set the scrollLeft
and scrollTop
values (lines 40 and 41) does not work for me. However, the following code works. Essentially, what you want to do, is compute the point that will be in the center of the page after scaling in page coordinates. The scrollbars have then be set in a way, that said point is in fact centered. This solution assumes, that originX
and originY
can be accessed in the ``touchend` event listener as well.
The following code should replace the lines 36-41.
// Compute the current center point in page coordinates
const pageCenterX = this.rootElement.nativeElement.clientWidth/2 + this.rootElement.nativeElement.scrollLeft;
const pageCenterY = this.rootElement.nativeElement.clientHeight/2 + this.rootElement.nativeElement.scrollTop;
// Compute the next center point in page coordinates
const centerX = (pageCenterX - this.originX) / pinchScale + this.originX;
const centerY = (pageCenterY - this.originY) / pinchScale + this.originY;
// Compute the ratios of the center point to the total scrollWidth/scrollHeight
const px = centerX / this.rootElement.nativeElement.scrollWidth;
const py = centerY / this.rootElement.nativeElement.scrollHeight;
// Scale
PDFViewerApplication.pdfViewer.currentScale *= pinchScale;
// Set the scrollbar positions using the percentages and the new scrollWidth/scrollHeight
this.rootElement.nativeElement.scrollLeft = this.rootElement.nativeElement.scrollWidth * px - this.rootElement.nativeElement.clientWidth/2;
this.rootElement.nativeElement.scrollTop = this.rootElement.nativeElement.scrollHeight * py - this.rootElement.nativeElement.clientHeight/2;
Thank you !
thx!
thx!
Did it work for you?
thx!
Did it work for you?
perfectly
in iphone, the origin gist code works perfectly. but in android phone, it would get stuck if I pinch too quickly.
I tried some methods(time throttle, mask layer, pinch distance throttle) to fix the problem and found out pinch distance throttle is the most appropriate solution.
let lastPinchDistance = 0;
const pinchStepLength = 50;
document.addEventListener("touchmove", (e) => {
if (initialPinchDistance <= 0 || e.touches.length < 2) { return; }
if (e.scale !== 1) { e.preventDefault(); }
const pinchDistance = Math.hypot((e.touches[1].pageX - e.touches[0].pageX), (e.touches[1].pageY - e.touches[0].pageY));
if (Math.abs(pinchDistance - lastPinchDistance) < pinchStepLength) {
return;
}
lastPinchDistance = pinchDistance;
const originX = startX + container.scrollLeft;
const originY = startY + container.scrollTop;
pinchScale = pinchDistance / initialPinchDistance;
viewer.style.transform = `scale(${pinchScale})`;
viewer.style.transformOrigin = `${originX}px ${originY}px`;
}, { passive: false });
in iphone, the origin gist code works perfectly. but in android phone, it would get stuck if I pinch too quickly.
I tried some methods(time throttle, mask layer, pinch distance throttle) to fix the problem and found out pinch distance throttle is the most appropriate solution.
Hello. If I increase the pdf to the maximum size it causes the application to freeze on android. Have you faced such a problem?
in iphone, the origin gist code works perfectly. but in android phone, it would get stuck if I pinch too quickly.
I tried some methods(time throttle, mask layer, pinch distance throttle) to fix the problem and found out pinch distance throttle is the most appropriate solution.Hello. If I increase the pdf to the maximum size it causes the application to freeze on android. Have you faced such a problem?
Yes, I just verified this bug. I don't see pdfjs has a config of max scale limit, so I guess this is what cause the problem.
my poor solution is to add a max scale limit in touchend event, here's my modified code:
const pinchMaxScale = 5;
container.addEventListener("touchend", (e) => {
if (initialPinchDistance <= 0) {
return;
}
viewer.style.transform = `none`;
viewer.style.transformOrigin = `unset`;
const newPinchScale =
PDFViewerApplication.pdfViewer.currentScale * pinchScale;
if (newPinchScale <= pinchMaxScale) {
PDFViewerApplication.pdfViewer.currentScale = newPinchScale;
const rect = container.getBoundingClientRect();
const dx = startX - rect.left;
const dy = startY - rect.top;
container.scrollLeft += dx * (pinchScale - 1);
container.scrollTop += dy * (pinchScale - 1);
}
reset();
});
в iphone код origin gist работает отлично. но в телефоне Android он застрянет, если я ущипну его слишком быстро.
Я попробовал несколько методов (дроссель времени, слой маски, дроссель расстояния зажима), чтобы решить проблему, и обнаружил, что дроссель расстояния зажима является наиболее подходящим решением.Привет. Если я увеличу pdf до максимального размера, это приведет к зависанию приложения на android. Вы сталкивались с такой проблемой?
Да, я только что проверил эту ошибку. Я не вижу, что pdfjs имеет конфигурацию максимального предела масштабирования, поэтому я предполагаю, что это причина проблемы.
Мое плохое решение - добавить ограничение максимального масштаба в событии touchend, вот мой измененный код:const pinchMaxScale = 5 ; контейнер . addEventListener ( "touchend" , ( e ) => { if ( initialPinchDistance <= 0 ) { return ; } viewer . style . transform = `none` ; viewer . style . transformOrigin = ` unset` ; const newPinchScale = PDFViewerApplication . pdfViewer . currentScale * pinchScale ; если ( newPinchScale <= pinchMaxScale ) { PDFViewerApplication . pdfViewer . currentScale = newPinchScale ; const rect = контейнер . getBoundingClientRect ( ) ; const dx = startX - прямоугольник . слева ; const dy = startY - прямоугольник . верх ; контейнер . scrollLeft + = dx * (pinchScale - 1 ) ; контейнер . scrollTop + = dy * ( pinchScale - 1 ) ; } reset ( ) ; } ) ;
This is what I tried in the first place and which unfortunately didn't work for me. It seems to me that this is due to the fact that after each increase, the files are re-rendered.
To fix the jerky rendering on touchend when using the viewer with the default 32px top bar, I used the code at the top of this page with line 39 modified like so:
const dy = startY - rect.top + 32;
Perhaps this will help someone else. :-)
Pinch Zoom on Android devices still doesn't work... :/ Works perfectly on iOS devices. I added this code to the end of viewer.html (before </body>
)
Code inside <script></script>
tags:
let pinchZoomEnabled = false;
function enablePinchZoom(pdfViewer) {
let startX = 0, startY = 0;
let initialPinchDistance = 0;
let pinchScale = 1;
const viewer = document.getElementById("viewer");
const container = document.getElementById("viewerContainer");
const reset = () => { startX = startY = initialPinchDistance = 0; pinchScale = 1; };
// Prevent native iOS page zoom
//document.addEventListener("touchmove", (e) => { if (e.scale !== 1) { e.preventDefault(); } }, { passive: false });
document.addEventListener("touchstart", (e) => {
if (e.touches.length > 1) {
startX = (e.touches[0].pageX + e.touches[1].pageX) / 2;
startY = (e.touches[0].pageY + e.touches[1].pageY) / 2;
initialPinchDistance = Math.hypot((e.touches[1].pageX - e.touches[0].pageX), (e.touches[1].pageY - e.touches[0].pageY));
} else {
initialPinchDistance = 0;
}
});
document.addEventListener("touchmove", (e) => {
if (initialPinchDistance <= 0 || e.touches.length < 2) { return; }
if (e.scale !== 1) { e.preventDefault(); }
const pinchDistance = Math.hypot((e.touches[1].pageX - e.touches[0].pageX), (e.touches[1].pageY - e.touches[0].pageY));
const originX = startX + container.scrollLeft;
const originY = startY + container.scrollTop;
pinchScale = pinchDistance / initialPinchDistance;
viewer.style.transform = `scale(${pinchScale})`;
viewer.style.transformOrigin = `${originX}px ${originY}px`;
}, { passive: false });
const pinchMaxScale = 5;
container.addEventListener("touchend", (e) => {
if (initialPinchDistance <= 0) {
return;
}
viewer.style.transform = `none`;
viewer.style.transformOrigin = `unset`;
const newPinchScale =
PDFViewerApplication.pdfViewer.currentScale * pinchScale;
if (newPinchScale <= pinchMaxScale) {
PDFViewerApplication.pdfViewer.currentScale = newPinchScale;
const rect = container.getBoundingClientRect();
const dx = startX - rect.left;
const dy = startY - rect.top;
container.scrollLeft += dx * (pinchScale - 1);
container.scrollTop += dy * (pinchScale - 1);
}
reset();
});
}
document.addEventListener('webviewerloaded', () => {
if('ontouchstart' in document) {
pinchZoomEnabled = true;
enablePinchZoom();
}
});
Any solutions? I also tried adding Hammer.js - unsuccessfully also...
So, it turns out 'webviewerloaded' event is not firing for me.
this works:
setTimeout(function(){
if (!pinchZoomEnabled) {
pinchZoomEnabled = true;
enablePinchZoom();
}
}, 0)
The code inside would probably be executed after the render is finished. Any ideas for improvement?
And any reason why this event or on this note, anyother event may not be thrown by the library?
If pdf is large in size(say 10-12MB ), the actual zooming occurs after you have completed the gesture while if pdf is small it zooms as soon as you start pinch gesture on android.
Если PDF-файл имеет большой размер (скажем, 10–12 МБ), фактическое масштабирование происходит после того, как вы завершили жест, а если PDF-файл маленький, он масштабируется, как только вы начинаете жест сжатия на Android.
please tell me, have you found any solution to this problem?
One solution would be to only zoom only the current page and save the scale value somewhere so that next pages renders with the new scale. Currently, I believe it's trying to resize whole pdf or part of the pdf which has been loaded which explains the delay. I do not have any idea on how to implement it.
This solution does not work correctly if we render the entire document at once, rather than one page. Has anyone tried to solve this problem?
Lazy loading would be the correct way. You would also need to ensure that if user scrolls a page after let's say 5 pages,free it from memory, but i believe pdfjs is already doing it. I better solution would be to just translate pdfjs when user is zooming and do not do actual zooming and store the value. When user has finished, do the actual zooming. Plus the code is resetting currentscale after every time. Instead we can set a maximum and minimum possible scale say 3 and 0.1. I noticed that google drive also do not do any zoom while user is pinching but it does when user has finished it.
It looks like the code is doing exactly the same thing except setting max and min scale
I have a question. As I touch to zoom out, I want to set my pdf page scale for min size (page fit)
How can I do. I waiting for your ask.
So I have set minScale and maxScale like this. Just replace touchend event
const pinchMaxScale = 5;
const pinchMinScale = 0.3;
document.addEventListener("touchend", (e) => {
if (initialPinchDistance <= 0) { return; }
viewer.style.transform = `none`;
viewer.style.transformOrigin = `unset`;
const newPinchScale = PDFViewerApplication.pdfViewer.currentScale * pinchScale;
if (newPinchScale <= pinchMaxScale && newPinchScale >= pinchMinScale) {
PDFViewerApplication.pdfViewer.currentScale = newPinchScale;
const rect = container.getBoundingClientRect();
const dx = startX - rect.left;
const dy = startY - rect.top;
container.scrollLeft += dx * (pinchScale - 1);
container.scrollTop += dy * (pinchScale - 1);
}else{
if(newPinchScale >= pinchMaxScale){
PDFViewerApplication.pdfViewer.currentScale = pinchMaxScale;
}else{
PDFViewerApplication.pdfViewer.currentScale = pinchMinScale;
}
}
reset();
});
I hope it will help someone else :-)
So this code is working fine but it may need a little update if you're using recent versions of mozilla/pdf.js
webviewerloaded
was not firing on iOS 13+ and Android 10+ when I tested because the event was fired using deprecated code.
In this case, you should be listening on DOMContentLoaded
instead of webviewerloaded
Replace
document.addEventListener('webviewerloaded', () => {
With
document.addEventListener('DOMContentLoaded', () => {
Hence the final code should be this:
let pinchZoomEnabled = false;
function enablePinchZoom(pdfViewer) {
let startX = 0,
startY = 0;
let initialPinchDistance = 0;
let pinchScale = 1;
const viewer = document.getElementById("viewer");
const container = document.getElementById("viewerContainer");
const reset = () => {
startX = startY = initialPinchDistance = 0;
pinchScale = 1;
};
// Prevent native iOS page zoom
//document.addEventListener("touchmove", (e) => { if (e.scale !== 1) { e.preventDefault(); } }, { passive: false });
document.addEventListener("touchstart", (e) => {
if (e.touches.length > 1) {
startX = (e.touches[0].pageX + e.touches[1].pageX) / 2;
startY = (e.touches[0].pageY + e.touches[1].pageY) / 2;
initialPinchDistance = Math.hypot(
e.touches[1].pageX - e.touches[0].pageX,
e.touches[1].pageY - e.touches[0].pageY
);
} else {
initialPinchDistance = 0;
}
});
document.addEventListener(
"touchmove",
(e) => {
if (initialPinchDistance <= 0 || e.touches.length < 2) {
return;
}
if (e.scale !== 1) {
e.preventDefault();
}
const pinchDistance = Math.hypot(
e.touches[1].pageX - e.touches[0].pageX,
e.touches[1].pageY - e.touches[0].pageY
);
const originX = startX + container.scrollLeft;
const originY = startY + container.scrollTop;
pinchScale = pinchDistance / initialPinchDistance;
viewer.style.transform = `scale(${pinchScale})`;
viewer.style.transformOrigin = `${originX}px ${originY}px`;
},
{ passive: false }
);
document.addEventListener("touchend", (e) => {
if (initialPinchDistance <= 0) {
return;
}
viewer.style.transform = `none`;
viewer.style.transformOrigin = `unset`;
PDFViewerApplication.pdfViewer.currentScale *= pinchScale;
const rect = container.getBoundingClientRect();
const dx = startX - rect.left;
const dy = startY - rect.top;
container.scrollLeft += dx * (pinchScale - 1);
container.scrollTop += dy * (pinchScale - 1);
reset();
});
}
document.addEventListener("DOMContentLoaded", () => {
if (!pinchZoomEnabled) {
pinchZoomEnabled = true;
enablePinchZoom();
}
});
It can be placed in a file called zoom.js
and you can just add this line before the final </body>
tag in viewer.html
file in the web folder.
<body>
---
---
<script src="zoom.js"></script>
</body>
Is this just suitable for viewer.html
? Can I utilize it within my customed viewer? Like a viewer in a div
using canvas backend?
Unfortunately pinch-to-zoom doesn't work if I put the viewer in an iframe :( Is there any kind of solution?
Is this just suitable for
viewer.html
? Can I utilize it within my customed viewer? Like a viewer in adiv
using canvas backend?
I think it should work but you would have to make appropriate changes as required by the back end that you make.
hello guys , when i implement and test it on my application ,it doesn't work
Thank you @meghrathod - It's working for BlazorWebView in MAUI Blazor
hi guys, if you use modern browser, please upgrade pdfjs to latest version or the supported version(version >=3.3.122, for more info see this PR), pdfjs now support pinch-to-zoom feature
.
if unfortunately, not use modern browser(for example, you have to use pdfjs version v2.x), this code work perfectly!
hi guys, if you use modern browser, please upgrade pdfjs to latest version or the supported version(version >=3.3.122, for more info see this PR), pdfjs now support
pinch-to-zoom feature
. if unfortunately, not use modern browser(for example, you have to use pdfjs version v2.x), this code work perfectly!
example mobile-viewer
not support pinch zoom, do you know how to enable?
mozilla/pdf.js#19004
https://github.com/mozilla/pdf.js/tree/master/examples/mobile-viewer
Thanks a lot.
I've tested it, but after a while it gets stuck again. No possibility to scroll again.