diff --git a/README.md b/README.md index b41033f..e5ee524 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,12 @@ # About -This is a fork of: https://github.com/NayamAmarshe/firefox-multi-touch-zoom +This is a fork of: https://github.com/bmarwane/chrome-multi-touch-zoom -I added a simple way to support mouse movement when zoomed-in by pressing SHIFT key and moving the mouse. - -I do not plan to actively support this extension. +I just updated the extension to work with manifest v3 as v2 is going to be deprecated soon! # Installation -- clone this repo or [download the code](https://github.com/bmarwane/chrome-multi-touch-zoom/archive/refs/heads/master.zip) - +- clone this repo - Open the extensions page - Turn on Developer Mode - Press 'Load Unpacked' button. diff --git a/extension/manifest.json b/extension/manifest.json index c38f2d2..ed2a2af 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,36 +1,32 @@ { - "manifest_version": 2, + "manifest_version": 3, "name": "Multi-touch Zoom", "version": "0.95", - // gecko stores browser specific things, no need for it using chrome - //"applications": { - // "gecko": { - // "id": "{90b5a72e-cdbd-49df-8304-5b5d6ea84a0f}", - // "strict_min_version": "57.0" - // } - //}, - "description": "Adds smooth multi-touch zoom that matches the behavior in Safari and Chrome", - - + "icons": { "48": "icons/icon-48.png", "96": "icons/icon-96.png", "192": "icons/icon-192.png" - }, - - "content_scripts": [{ + }, + + "content_scripts": [ + { "matches": [""], "js": ["pinch-zoom.js"], "run_at": "document_start" - }], - + } + ], + "permissions": [ - "storage", - "debugger" + "storage", + "activeTab" ], - - "options_ui": { - "page": "settings.html" - } -} + + "action": { + "default_popup": "settings.html" + }, + + "host_permissions": [""] + } + \ No newline at end of file diff --git a/extension/pinch-zoom.js b/extension/pinch-zoom.js index f0889e3..15a5687 100644 --- a/extension/pinch-zoom.js +++ b/extension/pinch-zoom.js @@ -1,25 +1,4 @@ -/** -EDITED FOR THE PURPOSE OF USING WITH CHROME ON LINUX -ONLY SHIFT+SCROLL FUNCTIONALITY IMPLEMENTED - -Multi-touch zoom extension for Firefox -Enables smooth pinch to zoom on desktop - -Requires Firefox 55 or greater - -@Author: George Corney / haxiomic -@Website: http://github.com/haxiomic -@Email: haxiomic@gmail.com - -Please report issues to the github repository - https://github.com/haxiomic/firefox-multi-touch-zoom - -Feel free to get in touch via email if you have any questions - -**/ - -// view scaling parameters and other options -const scaleMode = 1; // 0 = always high quality, 1 = low-quality while zooming +const scaleMode = 1; const minScale = 1.0; const maxScale = 10; const zoomSpeedMultiplier = 0.03 / 5; @@ -27,23 +6,20 @@ const overflowTimeout_ms = 400; const highQualityWait_ms = 40; const alwaysHighQuality = false; -let horizontalOriginShift = 0; // > 0 to the right, < 0 to the left -let verticalOriginShift = 0; // > 0 down, < 0 up +let horizontalOriginShift = 0; +let verticalOriginShift = 0; let originMoveRate = 10; -// settings -let shiftKeyZoom = true; // enable zoom with shift + scroll by default +let shiftKeyZoom = true; let pinchZoomSpeed = 0.7; let disableScrollbarsWhenZooming = false; -// state let pageScale = 1; let translationX = 0; let translationY = 0; let overflowTranslationX = 0; let overflowTranslationY = 0; -// elements let pageElement = document.documentElement; let wheelEventElement = document.documentElement; let scrollEventElement = window; @@ -54,54 +30,44 @@ function getScrollBoxElement() { return document.documentElement || document.body; } -// apply user settings chrome.storage.local.get([ - 'mtzoom_shiftkey', - 'mtzoom_speed', - 'mtzoom_disableScrollbarsWhenZooming', + 'mtzoom_shiftkey', + 'mtzoom_speed', + 'mtzoom_disableScrollbarsWhenZooming', ], function (res) { - if (res.mtzoom_shiftkey != null) { - shiftKeyZoom = res.mtzoom_shiftkey; - } - if (res.mtzoom_speed != null) { - pinchZoomSpeed = res.mtzoom_speed; - } - if (res.mtzoom_disableScrollbarsWhenZooming != null) { - disableScrollbarsWhenZooming = res.mtzoom_disableScrollbarsWhenZooming; - } + if (res.mtzoom_shiftkey != null) { + shiftKeyZoom = res.mtzoom_shiftkey; + } + if (res.mtzoom_speed != null) { + pinchZoomSpeed = res.mtzoom_speed; + } + if (res.mtzoom_disableScrollbarsWhenZooming != null) { + disableScrollbarsWhenZooming = res.mtzoom_disableScrollbarsWhenZooming; + } }); -// browser-hint optimization - I found this causes issues with some sites like maps.google.com -// pageElement.style.willChange = 'transform'; - - let mouseX, mouseY; let shoudFollowMouse = false; let canFollowMouse = false; - document.onmousemove = (e) => { - if(!canFollowMouse) return; + if (!canFollowMouse) return; if (shoudFollowMouse && mouseX && mouseY) { - //window.scrollBy(e.clientX - mouseX, e.clientY - mouseY); - horizontalOriginShift+= e.clientX - mouseX; - verticalOriginShift+= e.clientY - mouseY; + horizontalOriginShift += e.clientX - mouseX; + verticalOriginShift += e.clientY - mouseY; pageElement.style.setProperty('transform-origin', `${horizontalOriginShift}px ${verticalOriginShift}px`, 'important'); } - // Store current position mouseX = e.clientX; mouseY = e.clientY; }; - -// cmd + 0 or ctrl + 0 to restore zoom window.addEventListener('keydown', (e) => { - if (e.key == '0' && e.ctrlKey) { - resetScale(); + if (e.key == '0' && e.ctrlKey) { + resetScale(); return; - } + } shoudFollowMouse = !!e.shiftKey; }); @@ -110,45 +76,39 @@ window.addEventListener('keyup', (e) => { shoudFollowMouse = !!e.shiftKey; }); -// because scroll top/left are handled as integers only, we only read the translation from scroll once scroll has changed -// if we didn't, our translation would have ugly precision issues => setTranslationX(4.5) -> translationX = 4 let ignoredScrollLeft = null; let ignoredScrollTop = null; function updateTranslationFromScroll(){ - if (getScrollBoxElement().scrollLeft !== ignoredScrollLeft) { - translationX = -getScrollBoxElement().scrollLeft; - ignoredScrollLeft = null; - } - if (getScrollBoxElement().scrollTop !== ignoredScrollTop) { - translationY = -getScrollBoxElement().scrollTop; - ignoredScrollTop = null; - } + if (getScrollBoxElement().scrollLeft !== ignoredScrollLeft) { + translationX = -getScrollBoxElement().scrollLeft; + ignoredScrollLeft = null; + } + if (getScrollBoxElement().scrollTop !== ignoredScrollTop) { + translationY = -getScrollBoxElement().scrollTop; + ignoredScrollTop = null; + } } -// https://github.com/rochal/jQuery-slimScroll/issues/316 scrollEventElement.addEventListener(`scroll`, updateTranslationFromScroll, { capture: false, passive: false }); wheelEventElement.addEventListener(`wheel`, (e) => { - if (e.shiftKey && shiftKeyZoom) { - if (e.defaultPrevented) return; + if (e.shiftKey && shiftKeyZoom) { + if (e.defaultPrevented) return; - let x = e.clientX - getScrollBoxElement().offsetLeft; - let y = e.clientY - getScrollBoxElement().offsetTop; - // x in non-scrolling, non-transformed coordinates relative to the scrollBoxElement - // 0 is always the left side and is always the right side + let x = e.clientX - getScrollBoxElement().offsetLeft; + let y = e.clientY - getScrollBoxElement().offsetTop; - let deltaMultiplier = pinchZoomSpeed * zoomSpeedMultiplier; + let deltaMultiplier = pinchZoomSpeed * zoomSpeedMultiplier; - let newScale = pageScale + e.deltaY * deltaMultiplier; - let scaleBy = pageScale/newScale; + let newScale = pageScale + e.deltaY * deltaMultiplier; + let scaleBy = pageScale/newScale; - applyScale(scaleBy, x, y, false); + applyScale(scaleBy, x, y, false); - e.preventDefault(); - e.stopPropagation(); - } else { - // e.preventDefault(); - restoreControl(); - } + e.preventDefault(); + e.stopPropagation(); + } else { + restoreControl(); + } }, { capture: false, passive: false }); getScrollBoxElement().addEventListener(`mousemove`, restoreControl); @@ -156,189 +116,152 @@ getScrollBoxElement().addEventListener(`mousedown`, restoreControl); let controlDisabled = false; function disableControl() { - if (controlDisabled) return; + if (controlDisabled) return; - if (disableScrollbarsWhenZooming) { - let verticalScrollBarWidth = window.innerWidth - pageElement.clientWidth; - let horizontalScrollBarWidth = window.innerHeight - pageElement.clientHeight; + if (disableScrollbarsWhenZooming) { + let verticalScrollBarWidth = window.innerWidth - pageElement.clientWidth; + let horizontalScrollBarWidth = window.innerHeight - pageElement.clientHeight; - // disable scrolling for performance - pageElement.style.setProperty('overflow', 'hidden', 'important'); + pageElement.style.setProperty('overflow', 'hidden', 'important'); - // since we're disabling a scrollbar we need to apply a margin to replicate the offset (if any) it introduced - // this prevent the page from being shifted about as the scrollbar is hidden and shown - pageElement.style.setProperty('margin-right', verticalScrollBarWidth + 'px', 'important'); - pageElement.style.setProperty('margin-bottom', horizontalScrollBarWidth + 'px', 'important'); - } + pageElement.style.setProperty('margin-right', verticalScrollBarWidth + 'px', 'important'); + pageElement.style.setProperty('margin-bottom', horizontalScrollBarWidth + 'px', 'important'); + } - // document.body.style.pointerEvents = 'none'; - controlDisabled = true; + controlDisabled = true; } function restoreControl() { - if (!controlDisabled) return; - // scrolling must be enabled for panning - pageElement.style.overflow = 'auto'; - pageElement.style.marginRight = ''; - pageElement.style.marginBottom = ''; - // document.body.style.pointerEvents = ''; - controlDisabled = false; + if (!controlDisabled) return; + + pageElement.style.overflow = 'auto'; + pageElement.style.marginRight = ''; + pageElement.style.marginBottom = ''; + + controlDisabled = false; } let qualityTimeoutHandle = null; let overflowTimeoutHandle = null; function updateTransform(scaleModeOverride, shouldDisableControl) { - if (shouldDisableControl == null) { - shouldDisableControl = true; - } - - let sm = scaleModeOverride == null ? scaleMode : scaleModeOverride; - - if (sm === 0 || alwaysHighQuality) { - // scaleX/scaleY - pageElement.style.setProperty('transform', `scaleX(${pageScale}) scaleY(${pageScale})`, 'important'); - } else { - // perspective (reduced quality but faster) - let p = 1; // what's the best value here? - let z = p - p/pageScale; - - pageElement.style.setProperty('transform', `perspective(${p}px) translateZ(${z}px)`, 'important'); - - // wait a short period before restoring the quality - // we use a timeout for trackpad because we can't detect when the user has finished the gesture on the hardware - // we can only detect gesture update events ('wheel' + ctrl) - window.clearTimeout(qualityTimeoutHandle); - qualityTimeoutHandle = setTimeout(function(){ - pageElement.style.setProperty('transform', `scaleX(${pageScale}) scaleY(${pageScale})`, 'important'); - }, highQualityWait_ms); - } - - pageElement.style.setProperty('transform-origin', `${horizontalOriginShift}px ${verticalOriginShift}px`, 'important'); - - // hack to restore normal behavior that's upset after applying the transform - pageElement.style.position = `relative`; - pageElement.style.height = `100%`; - - // when translation is positive, the offset is applied via left/top positioning - // negative translation is applied via scroll - if (minScale < 1) { - pageElement.style.setProperty('left', `${Math.max(translationX, 0) - overflowTranslationX}px`, 'important'); - pageElement.style.setProperty('top', `${Math.max(translationY, 0) - overflowTranslationY}px`, 'important'); - } - - // weird performance hack - is it batching the changes? - pageElement.style.transitionProperty = `transform, left, top`; - pageElement.style.transitionDuration = `0s`; - - if (shouldDisableControl) { - disableControl(); - clearTimeout(overflowTimeoutHandle); - overflowTimeoutHandle = setTimeout(function(){ - restoreControl(); - }, overflowTimeout_ms); - } + if (shouldDisableControl == null) { + shouldDisableControl = true; + } + + let sm = scaleModeOverride == null ? scaleMode : scaleModeOverride; + + if (sm === 0 || alwaysHighQuality) { + pageElement.style.setProperty('transform', `scaleX(${pageScale}) scaleY(${pageScale})`, 'important'); + } else { + let p = 1; + let z = p - p/pageScale; + + pageElement.style.setProperty('transform', `perspective(${p}px) translateZ(${z}px)`, 'important'); + + window.clearTimeout(qualityTimeoutHandle); + qualityTimeoutHandle = setTimeout(function(){ + pageElement.style.setProperty('transform', `scaleX(${pageScale}) scaleY(${pageScale})`, 'important'); + }, highQualityWait_ms); + } + + pageElement.style.setProperty('transform-origin', `${horizontalOriginShift}px ${verticalOriginShift}px`, 'important'); + + pageElement.style.position = `relative`; + pageElement.style.height = `100%`; + + pageElement.style.transitionProperty = `transform, left, top`; + pageElement.style.transitionDuration = `0s`; + + if (shouldDisableControl) { + disableControl(); + clearTimeout(overflowTimeoutHandle); + overflowTimeoutHandle = setTimeout(function(){ + restoreControl(); + }, overflowTimeout_ms); + } } function applyScale(scaleBy, x_scrollBoxElement, y_scrollBoxElement) { - // x/y coordinates in untransformed coordinates relative to the scroll container - // if the container is the window, then the coordinates are relative to the window - // ignoring any scroll offset. The coordinates do not change as the page is transformed - - function getTranslationX(){ return translationX; } - function getTranslationY(){ return translationY; } - function setTranslationX(v) { - // clamp v to scroll range - // this limits minScale to 1 - v = Math.min(v, 0); - v = Math.max(v, -(getScrollBoxElement().scrollWidth - getScrollBoxElement().clientWidth)); - - translationX = v; - - getScrollBoxElement().scrollLeft = Math.max(-v, 0); - ignoredScrollLeft = getScrollBoxElement().scrollLeft; - - // scroll-transform what we're unable to apply - // either there is no scroll-bar or we want to scroll past the end - overflowTranslationX = v < 0 ? Math.max((-v) - (getScrollBoxElement().scrollWidth - getScrollBoxElement().clientWidth), 0) : 0; - } - function setTranslationY(v) { - // clamp v to scroll range - // this limits minScale to 1 - v = Math.min(v, 0); - v = Math.max(v, -(getScrollBoxElement().scrollHeight - getScrollBoxElement().clientHeight)); - - translationY = v; - - getScrollBoxElement().scrollTop = Math.max(-v, 0); - ignoredScrollTop = getScrollBoxElement().scrollTop; - - overflowTranslationY = v < 0 ? Math.max((-v) - (getScrollBoxElement().scrollHeight - getScrollBoxElement().clientHeight), 0) : 0; - } - - // resize pageElement - let pageScaleBefore = pageScale; - pageScale *= scaleBy; - pageScale = Math.min(Math.max(pageScale, minScale), maxScale); - let effectiveScale = pageScale/pageScaleBefore; - - if(pageScale === 1) { + function getTranslationX(){ return translationX; } + function getTranslationY(){ return translationY; } + function setTranslationX(v) { + v = Math.min(v, 0); + v = Math.max(v, -(getScrollBoxElement().scrollWidth - getScrollBoxElement().clientWidth)); + + translationX = v; + + getScrollBoxElement().scrollLeft = Math.max(-v, 0); + ignoredScrollLeft = getScrollBoxElement().scrollLeft; + + overflowTranslationX = v < 0 ? Math.max((-v) - (getScrollBoxElement().scrollWidth - getScrollBoxElement().clientWidth), 0) : 0; + } + function setTranslationY(v) { + v = Math.min(v, 0); + v = Math.max(v, -(getScrollBoxElement().scrollHeight - getScrollBoxElement().clientHeight)); + + translationY = v; + + getScrollBoxElement().scrollTop = Math.max(-v, 0); + ignoredScrollTop = getScrollBoxElement().scrollTop; + + overflowTranslationY = v < 0 ? Math.max((-v) - (getScrollBoxElement().scrollHeight - getScrollBoxElement().clientHeight), 0) : 0; + } + + let pageScaleBefore = pageScale; + pageScale *= scaleBy; + pageScale = Math.min(Math.max(pageScale, minScale), maxScale); + let effectiveScale = pageScale/pageScaleBefore; + + if (pageScale === 1) { canFollowMouse = false; } else { canFollowMouse = true; } - if(pageScale === 1 && (horizontalOriginShift || verticalOriginShift)) { + if (pageScale === 1 && (horizontalOriginShift || verticalOriginShift)) { horizontalOriginShift = 0; verticalOriginShift = 0; } - // when we hit min/max scale we can early exit - if (effectiveScale === 1) return; + if (effectiveScale === 1) return; - updateTransform(null, null); + updateTransform(null, null); - //zx and zy are the absolute coordinates of the mouse on the screen - let zx = x_scrollBoxElement; - let zy = y_scrollBoxElement; + let zx = x_scrollBoxElement; + let zy = y_scrollBoxElement; - // calculate new xy-translation - let tx = getTranslationX(); - tx = (tx - zx) * (effectiveScale) + zx; + let tx = getTranslationX(); + tx = (tx - zx) * (effectiveScale) + zx; - let ty = getTranslationY(); - ty = (ty - zy) * (effectiveScale) + zy; + let ty = getTranslationY(); + ty = (ty - zy) * (effectiveScale) + zy; - // apply new xy-translation - setTranslationX(tx); - setTranslationY(ty); + setTranslationX(tx); + setTranslationY(ty); - updateTransform(null, null); + updateTransform(null, null); } function resetScale() { - // reset state - pageScale = 1; - translationX = 0; - translationY = 0; - overflowTranslationX = 0; - overflowTranslationY = 0; + pageScale = 1; + translationX = 0; + translationY = 0; + overflowTranslationX = 0; + overflowTranslationY = 0; horizontalOriginShift = 0; verticalOriginShift = 0; - let scrollLeftBefore = getScrollBoxElement().scrollLeft; - let scrollLeftMaxBefore = getScrollBoxElement().scrollMax; - let scrollTopBefore = getScrollBoxElement().scrollTop; - let scrollTopMaxBefore = (getScrollBoxElement().scrollHeight - getScrollBoxElement().clientHeight); - updateTransform(0, false, false); + let scrollLeftBefore = getScrollBoxElement().scrollLeft; + let scrollLeftMaxBefore = getScrollBoxElement().scrollMax; + let scrollTopBefore = getScrollBoxElement().scrollTop; + let scrollTopMaxBefore = (getScrollBoxElement().scrollHeight - getScrollBoxElement().clientHeight); + updateTransform(0, false, false); - // restore scroll - getScrollBoxElement().scrollLeft = (scrollLeftBefore/scrollLeftMaxBefore) * (getScrollBoxElement().scrollWidth - getScrollBoxElement().clientWidth); - getScrollBoxElement().scrollTop = (scrollTopBefore/scrollTopMaxBefore) * (getScrollBoxElement().scrollHeight - getScrollBoxElement().clientHeight); + getScrollBoxElement().scrollLeft = (scrollLeftBefore/scrollLeftMaxBefore) * (getScrollBoxElement().scrollWidth - getScrollBoxElement().clientWidth); + getScrollBoxElement().scrollTop = (scrollTopBefore/scrollTopMaxBefore) * (getScrollBoxElement().scrollHeight - getScrollBoxElement().clientHeight); - updateTranslationFromScroll(); + updateTranslationFromScroll(); - // undo other css changes - pageElement.style.overflow = ''; - // document.body.style.pointerEvents = ''; + pageElement.style.overflow = ''; }