Генеральный план
Увеличить
Исходный
Уменьшить
Полный
  document.addEventListener("DOMContentLoaded", function() {
            let img = document.querySelector('.bg_map'),
                el = document.querySelector('.block_map'),
                wrapper = document.querySelector('.wrapper_map'),
                plus = document.querySelector('.button_plus'),
                minus = document.querySelector('.button_minus'),
                source = document.querySelector('.button_source'),
                fullscreen = document.querySelector('.button_screen');
            let imgRect = img.getBoundingClientRect(),
                elRect = el.getBoundingClientRect();
            let pos = {
                x: 0,
                y: 0
            }
            const sizeImg = {
                w: img.offsetWidth, 
                h: img.offsetHeight 
            };
            let sizeEl = {
                w: el.offsetWidth, 
                h: el.offsetHeight
            };
            let scale = 1;  
            const mat = new DOMMatrix();
            const maxScale = 4;
            const stepScale = 0.1;
            const stepScaleTouch = 0.1;
            function filterPos(a, b, t) {
              if (a > 0) return 0;
              else if (a < b) return b;
              else return a;
            }
            function filterScale(a) {
                let minScale = Math.max(sizeEl.w/sizeImg.w,sizeEl.h/sizeImg.h);
                scale = Math.min(Math.max(minScale, a), maxScale);
                mat.a = scale;
                mat.d = scale;
                (maxScale == scale ? plus.classList.add("not_active") : plus.classList.remove('not_active'));
                (minScale == scale ? minus.classList.add("not_active") : minus.classList.remove('not_active'));
            }
                function zoomAt(d, x, y) {
                    let target = {  // коорд курсора отн исходного img
                        x: x / scale,
                        y: y / scale
                    }

                    scale += d;  // В±0.05
                    filterScale(scale);
                  
                    let offset = {  // разница м/у коорд курсора текущие/будущие + текущие смещение
                        x: x - target.x * scale + mat.e,
                        y: y - target.y * scale + mat.f
                    }
                    mat.e = filterPos(offset.x, (sizeEl.w - sizeImg.w*scale));
                    mat.f = filterPos(offset.y, (sizeEl.h - sizeImg.h*scale));
                    img.style.transform = mat.toString();
                }

                let start = { x: 0, y: 0 };
                let scroll = { x: 0, y: 0 };                

                const pan = (event) => {
                    imgRect = img.getBoundingClientRect();
                    elRect = el.getBoundingClientRect();                    
                    if (event.type === "pointerdown") {
                        document.addEventListener("pointermove", pan);
                        document.addEventListener("pointerup", pan);
                        // коорд курсора отн окна браузера
                        start.x = event.clientX;
                        start.y = event.clientY;
                        // смещение img отн el
                        scroll.x = imgRect.left - elRect.left;
                        scroll.y = imgRect.top - elRect.top;                        
                    }
                    if (event.type === "pointerup") {
                        document.removeEventListener("pointermove", pan);
                        document.removeEventListener("pointerup", pan);
                    }
                    if (event.type === "pointermove") { 
                      
                     if (evCache.length == 1) {   // если в кэше 1 указатель
                        pos.x = scroll.x + (event.clientX - start.x);
                        pos.y = scroll.y + (event.clientY - start.y);
                      
                        mat.e = filterPos(pos.x, (elRect.width - imgRect.width));
                        mat.f = filterPos(pos.y, (elRect.height - imgRect.height));
                      
                        if (!document.fullscreenElement) {
                            ((mat.f == pos.y) ? wrapper.classList.add("touch_action") : wrapper.classList.remove("touch_action"))
                        }
                      
                        img.style.transform = mat.toString();
                     }
                                          
                      
                    }
                    event.preventDefault();
                }
                el.addEventListener("pointerdown", pan);
    
                const wheelAt = (event) => {
                    event.preventDefault();              
                    imgRect = img.getBoundingClientRect();
                    elRect = el.getBoundingClientRect();
                                    
                    let offset = {  // коорд курсора отн img
                        x: event.clientX - imgRect.left,
                        y: event.clientY - imgRect.top
                    }
                    let delta = Math.max(-1,Math.min(1,event.deltaY)) * -stepScale; // ±0.05
                    zoomAt(delta, offset.x, offset.y);
                   //    document.getElementsByTagName('pre')[0].innerHTML += event.clientX + " и " + imgRect.left+"
"; } el.addEventListener("wheel", wheelAt); let evCache = new Array(); let prevDiff = -1; function zoomTouchDown(ev) { // добавление в конец кэша evCache.push(ev); } function zoomTouchMove(ev) { // обновление в кэше события for (var i = 0; i < evCache.length; i++) { if (ev.pointerId == evCache[i].pointerId) { evCache[i] = ev; break; } } if (evCache.length == 2) { // если в кэше 2 указателя // расстояние между двумя указателями let curDiff = Math.sqrt(Math.pow(evCache[1].clientX - evCache[0].clientX, 2) + Math.pow(evCache[1].clientY - evCache[0].clientY, 2)); imgRect = img.getBoundingClientRect(); elRect = el.getBoundingClientRect(); let delta = 0; let offset = { // коорд курсора отн img x: (evCache[1].clientX + evCache[0].clientX)/2 - imgRect.left, y: (evCache[1].clientY + evCache[0].clientY)/2 - imgRect.top } if (prevDiff > 0) { if (curDiff > prevDiff) { // если расстояние увеличилось delta += stepScaleTouch; // ±0.05 } if (curDiff < prevDiff) { // если расстояние уменьшилось delta -= stepScaleTouch; // ±0.05 } } prevDiff = curDiff; // document.getElementsByTagName('pre')[0].innerHTML += (evCache[1].clientX + evCache[0].clientX)/2 + " и " + imgRect.left+"
"; zoomAt(delta, offset.x, offset.y); } } function zoomTouchRem(ev) { // удаляем указатели из кэша и сбрасываем remove_event(ev); if (evCache.length < 2) prevDiff = -1; // если в кэше меньше 2 указателей } function remove_event(ev) { // удалить событие из кэша for (var i = 0; i < evCache.length; i++) { if (evCache[i].pointerId == ev.pointerId) { evCache.splice(i, 1); break; } } } // обработчики события el.onpointerdown = zoomTouchDown; el.onpointermove = zoomTouchMove; // pointer{up,cancel,out,leave} те же el.onpointerup = zoomTouchRem; el.onpointercancel = zoomTouchRem; el.onpointerout = zoomTouchRem; el.onpointerleave = zoomTouchRem; });