<div class="main">
    <div class="main__inner">
        <h1 class="main__ttl">
            <a href="https://nocebo.jp/post-1184/" target="_blank">ZoomSlider[拡大機能付きのスライダー]</a> 
        </h1>
        <div class="slider">
            <nav class="slider__nav">
                <a href="javascript:;" class="slider__prev"><i class="fas fa-angle-left"></i></a>
                <a href="javascript:;" class="slider__zoom"><i class="fas fa-search-plus"></i></a>
                <a href="javascript:;" class="slider__next"><i class="fas fa-angle-right"></i></a>
            </nav>
            <div class="slider__inner">
                <div class="slider__main">
                    <ul class="slider__list">
                        <li class="slider__elm slider__elm--01">
                            <a href="javascript:;">
                                <img src="https://nocebo.jp/data/zoomslider/img/01.jpg" alt="images">
                            </a>
                        </li>
                        <li class="slider__elm slider__elm--02">
                            <a href="javascript:;">
                                <img src="https://nocebo.jp/data/zoomslider/img/02.jpg" alt="images">
                            </a>
                        </li>
                        <li class="slider__elm slider__elm--03">
                            <a href="javascript:;">
                                <img src="https://nocebo.jp/data/zoomslider/img/03.jpg" alt="images">
                            </a>
                        </li>
                    </ul>
                </div>
            </div>
        </div>
        <!-- ====================================__  .slider  ============================= -->
        <div class="modal">
            <nav class="modal__nav">
                <a href="javascript:;" class="modal__close"><i class="fas fa-times"></i></a>
            </nav>
            <div class="modal__elm">
                <div class="zoom">
                    <div class="zoom__info">
                        <img src="https://nocebo.jp/data/zoomslider/img/01.jpg" alt="images">
                        <div class="zoom__origin zoom__origin--02"></div>
                        <div class="zoom__area"></div>
                        <div class="zoom__data">
                            <p class="zoom__op">origin<span></span></p>
                            <p class="zoom__cp">move<span></span></p>
                            <p class="zoom__scale">scale<span></span></p>
                        </div>
                    </div>
                    <div class="zoom__elm">
                        <div class="zoom__origin zoom__origin--01"></div>
                        <img src="https://nocebo.jp/data/zoomslider/img/01.jpg" alt="images">
                    </div>
                </div>
                <!-- ====================================__  .zoom  ============================= -->
            </div>
        </div>
        <!-- ====================================__  .modal  ============================= -->
    </div>
</div>
<!-- ====================================__  .main  ============================= -->
*
{
    box-sizing: border-box;

    text-decoration: none;

    touch-action: none;
}

img
{
    user-select: none;
    pointer-events: none;
}


$baseWidth: 768px;

.contents
{
    height: 100%;
}

.main
{
    display: flex;

    width: $baseWidth;
    min-height: 100%;
    margin: 0 auto;

    background-color: #eee;

    align-items: center;

    .main__inner
    {
        padding: 50px 0;
    }

    .main__ttl
    {
        font-size: 30px;
        font-weight: bold;

        margin-bottom: 20px;
        margin-left: 10px;

        color: #000;
    }
}


.slider
{
    position: relative;

    width: $baseWidth;

    .slider__inner
    {
        overflow: hidden;

        width: 100%;
    }

    .slider__elm
    {
        img
        {
            vertical-align: bottom;
        }
    }

    .slider__nav
    {
        position: absolute;
        z-index: 10;
        bottom: 0;

        display: flex;

        width: 100%;

        justify-content: space-between;

        a
        {
            font-size: 30px;
            font-weight: bold;

            display: block;

            width: 33.2%;
            padding: 25px;
            padding-bottom: 20px;

            text-align: center;
            text-decoration: none;

            color: #000;
            background-color: rgba(#fff, .7);

            &:hover
            {
                background-color: rgba(#fff, 1);
            }
        }
    }

    .slider__list
    {
        display: flex;

        width: max-content;

        li
        {
            width: $baseWidth;

            img
            {
                width: 100%;

                pointer-events: none;
            }
        }
    }
}


.modal
{
    position: fixed;
    z-index: -1;
    top: 0;
    left: 0;

    visibility: hidden;

    width: 100%;
    height: 100%;

    transition: all .2s linear;

    opacity: 0;
    background-color: rgba(0, 0, 0, .9);

    &.active
    {
        z-index: 1000;

        visibility: visible;

        opacity: 1;
    }

    .modal__nav
    {
        position: absolute;
        top: 30px;
        right: 20px;
    }

    .modal__close
    {
        font-size: 50px;

        position: fixed;
        z-index: 10000;
        top: 10px;
        right: 10px;

        width: 80px;
        height: 80px;
        padding: 23px;
        padding-top: 15px;

        color: #000;
        border-radius: 3px;
        border-radius: 100%;
        background-color: #fff;
    }

    .modal__elm
    {
        display: flex;

        height: 100%;

        align-items: center;
    }
}

.zoom
{
    position: relative;

    overflow: hidden;

    width: $baseWidth;
    height: $baseWidth;
    margin: 0 auto;

    border: 1px solid #ccc;

    .zoom__elm
    {
        width: 100%;

        &:hover
        {
            cursor: grab;
        }

        img
        {
            width: 100%;

            vertical-align: bottom;
        }
    }

    .zoom__info
    {
        position: absolute;
        z-index: 10;
        top: 1%;
        left: 1%;

        width: $baseWidth / 5;
        height: $baseWidth / 5;

        border: 1px solid #ccc;
        background-color: rgba(255, 255, 255, .4);

        img
        {
            width: 100%;

            opacity: .5;

            filter: grayscale(50%);
        }
    }

    .zoom__origin
    {
        position: absolute;
        z-index: 100;
        top: 0;
        left: 0;

        width: 20px;
        height: 20px;
        margin-top: -10px;
        margin-left: -10px;

        border-radius: 100%;
        background-color: rgba(255, 0, 0, .5);

        &.zoom__origin--02
        {
            width: 10px;
            height: 10px;
            margin-top: -5px;
            margin-left: -5px;
        }
    }

    .zoom__area
    {
        position: absolute;
        top: 0;
        left: 0;

        width: 100%;
        height: 100%;

        transform-origin: 100% 100%;

        border: 1px solid #ccc;
        background-color: rgba(0, 0, 0, .3);
    }

    .zoom__data
    {
        font-size: 15px;

        position: absolute;
        top: -1px;
        left: $baseWidth / 5 + 10;

        width: 200px;

        text-align: left;

        color: #fff;

        p
        {
            line-height: 1.3;

            margin-bottom: 5px;
        }
        span
        {
            display: inline-block;

            margin-left: 5px;
        }
    }
}
View Compiled
// /* ===========================================================
// # Point
// =========================================================== */
class Point {
    constructor(x = 0, y = 0) {
        this.x = x || 0;
        this.y = y || 0;
    }
    static distance(a, b) {
        const dx = a.x - b.x;
        const dy = a.y - b.y;
        return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
    }
    static interpolate(p1, p2, t) {
        const x = p1.x * (1 - t) + p2.x * t;
        const y = p1.y * (1 - t) + p2.y * t;
        return new Point(x, y);
    }
    add(p) {
        this.x += p.x;
        this.y += p.y;
        return this;
    }
    subtract(p) {
        this.x -= p.x;
        this.y -= p.y;
        return this;
    }
    clone() {
        return new Point(this.x, this.y);
    }
}
// /* ===========================================================
// # EventTouch
// =========================================================== */
class EventTouch {
    constructor() {
        if (EventTouch.instance) return;
        EventTouch.instance = this;
        // -----------------------------
        this.isDown = false; //PC用

        this.currentPoint = new Point();
        this.startPoint = new Point();
        this.diffPoint = new Point();

        this.touchesdiff = [];
        this.touchesStartPoints = [new Point(), new Point()];
        this.touchesCurrentPoints = [new Point(), new Point()];

        this.scale = 1;

        // PC
        document.addEventListener("mousedown", this.onStart.bind(this), false);
        document.addEventListener("mousemove", this.onMove.bind(this), false);
        document.addEventListener("mouseup", this.onUp.bind(this), false);
        // SP
        document.addEventListener("touchstart", this.onStart.bind(this), false);
        document.addEventListener("touchmove", this.onMove.bind(this), false);
        document.addEventListener("touchend", this.onUp.bind(this), false);

        document.addEventListener("gesturechange", this.onGestureChange.bind(this), false);
    }

    originalEvent(e) {
        // jquery使用時用にoriginalEvent取得
        return (e = e.originalEvent ? e.originalEvent : e);
    }
    mouseDelta(e) {
        return e.deltaY ? -(e.deltaY) : e.wheelDelta ? e.wheelDelta : -(e.detail);
    }

    clientPosition(e) {
        this.originalEvent(e);
        if (e.touches) {
            return new Point(e.touches[0].clientX, e.touches[0].clientY);
        } else {
            return new Point(e.clientX, e.clientY);
        }
    }

    onStart(e) {
        this.originalEvent(e);
        this.isDown = true;
        this.diffPoint = new Point();
        this.startPoint = this.clientPosition(e);

        if (!e.touches) return;
        for (let i = e.touches.length - 1; i >= 0; i--) {
            this.touchesStartPoints[i] = new Point(e.touches[i].clientX, e.touches[i].clientY);
        }
    }

    onMove(e) {
        this.originalEvent(e);
        this.currentPoint = this.clientPosition(e);

        if (!this.isDown) return;
        this.diffPoint = new Point(this.currentPoint.x, this.currentPoint.y).subtract(this.startPoint);

        if (!e.touches) return;
        for (let i = e.touches.length - 1; i >= 0; i--) {
            this.touchesCurrentPoints[i] = new Point(e.touches[i].clientX, e.touches[i].clientY);
        }
    }

    onUp() {
        this.isDown = false;
    }

    onGestureChange(e) {
        this.originalEvent(e);
        let scale = 1;
        if (e.scale) {
            scale = e.scale;
        } else {
            const startDistance = Point.distance(this.touchesStartPoints[0], this.touchesStartPoints[1]);
            const currentDistance = Point.distance(this.touchesCurrentPoints[0], this.touchesCurrentPoints[1]);
            scale = currentDistance / startDistance;
        }
        this.scale = scale;
    }
}
// /* ===========================================================
// # Slider
// =========================================================== */
class Slider {
    constructor() {
        // -----------------------------
        this.slider = document.querySelector(".slider");
        this.slider__list = document.querySelector(".slider__list");
        this.slider__main = document.querySelector(".slider__main");
        this.slider__prev = document.querySelector(".slider__prev");
        this.slider__next = document.querySelector(".slider__next");
        this.slider__zoom = document.querySelector(".slider__zoom");
        this.slider__elm = document.querySelectorAll(".slider .slider__elm");
        // -----------------------------
        this.state = "ready";
        this.currentID = 1;
        // -----------------------------
        this.factor = 0.15; // イージング 係数
        this.fraction = 0.001; // スライド切り替え終点位置の繰り上げ・切り下げ 端数
        this.sliderWidth = 0; //スライド自体の幅
        this.responseRatio = 0.1; // mouse&touchmove時にスライドorもと位置に戻すかの判定
        // -----------------------------
        this.currentPoint = new Point(); // 現在位置
        this.targetPoint = new Point(); // 目標位置
        // -----------------------------
        this.resize();
        this.events();
        this.elements();
        this.update();
    }

    events() {
        this.slider__prev.addEventListener("click", () => {
            this.slide("prev");
        }, false);
        this.slider__next.addEventListener("click", () => {
            this.slide("next");
        }, false);
        this.slider__zoom.addEventListener("click", () => {
            Modal.instance.open(this.currentID);
        }, false);

        this.slider__main.addEventListener("mousemove", this.onMove.bind(this), false);
        this.slider__main.addEventListener("touchmove", this.onMove.bind(this), false);
        this.slider__main.addEventListener("mouseup", this.onUp.bind(this), false);
        this.slider__main.addEventListener("touchend", this.onUp.bind(this), false);

        window.addEventListener("resize", this.resize.bind(this), false);
    }

    resize() {
        this.sliderWidth = this.slider.clientWidth;
        Object.assign(this.slider__main.style, {
            transform: `translate(${-this.sliderWidth}px, 0px)`
        });
    }

    onMove() {
        if (this.state != "ready") return;
        if (!EventTouch.instance.isDown) return;
        this.targetPoint.x = EventTouch.instance.diffPoint.x;
    }

    onUp() {
        if (this.state != "ready") return;
        const ratio = Math.abs(EventTouch.instance.diffPoint.x) / this.sliderWidth;
        if (ratio < this.responseRatio) {
            this.targetPoint.x = 0;
            return;
        }
        const state = (Math.sign(EventTouch.instance.diffPoint.x) != 1) ? "next" : "prev";
        this.slide(state);
    }

    clear() {
        this.currentPoint = new Point();
        this.targetPoint = new Point();
        this.state = "ready";
    }

    elements() {
        const slider__elm = document.querySelectorAll(".slider .slider__elm");
        const firstNode = slider__elm[0];
        const lastNode = slider__elm[slider__elm.length - 1];

        if (this.state == "next") {
            this.slider__list.append(firstNode);
        } else {
            this.slider__list.insertBefore(lastNode, firstNode);
        }
    }

    slide(_state) {
        if (this.state != "ready") return;
        this.state = _state;

        if (_state == "prev") {
            this.targetPoint.x = this.sliderWidth;
            this.currentID--;
        }
        if (_state == "next") {
            this.targetPoint.x = -this.sliderWidth;
            this.currentID++;
        }

        if (this.currentID < 1) this.currentID = this.slider__elm.length;
        if (this.currentID > this.slider__elm.length) this.currentID = 1;
    }

    update() {
        window.requestAnimationFrame(this.update.bind(this));

        this.currentPoint.x += Number(this.targetPoint.x - this.currentPoint.x) * this.factor;
        const ratio = Math.abs(this.currentPoint.x) / this.sliderWidth;

        if (ratio > (1.0 - this.fraction)) {
            this.currentPoint.x = this.targetPoint.x;
            this.elements();
            this.clear();
        }
        if (ratio < this.fraction) {
            this.currentPoint.x = this.targetPoint.x = 0;
        }

        Object.assign(this.slider__list.style, {
            transform: `translate(${this.currentPoint.x}px, 0px)`
        });
    }
}
// /* ===========================================================
// # Modal
// =========================================================== */
class Modal {
    constructor() {
        if (Modal.instance) return;
        Modal.instance = this;
        // -----------------------------
        this.modal = document.querySelector(".modal");
        this.modal__close = document.querySelector(".modal__close");
        this.delay = 500;
        this.events();
    }

    events() {
        this.modal__close.addEventListener("click", () => {
            this.close();
        }, false);
    }

    open(_id) {
        const id = (`00${_id}`).slice(-2);
        Array.from(document.querySelectorAll(".zoom img"), (e) => {
            e.src = `https://nocebo.jp/data/zoomslider/img/${id}.jpg`;
        });
        this.modal.classList.add("active");
    }

    close() {
        this.modal.classList.remove("active");
        window.setTimeout(() => {
            Zoom.instance.resetScale();
        }, this.delay);
    }
}

// /* ===========================================================
// # Timer
// =========================================================== */
class Timer {
    constructor(_time) {
        this.time = _time;
        this.isTimeOut = false;

        this.Obj = window.setTimeout(() => {
            this.isTimeOut = true;
        }, this.time);
    }

    reset() {
        this.isTimeOut = false;
        this.clear();
        this.Obj = window.setTimeout(() => {
            this.isTimeOut = true;
        }, this.time);
    }

    clear() {
        window.clearTimeout(this.Obj);
    }
}
// /* ===========================================================
// # Zoom
// =========================================================== */
class Zoom {
    constructor() {
        if (Zoom.instance) return;
        Zoom.instance = this;
        // -----------------------------
        this.zoom = document.querySelector(".zoom");
        this.zoom__elm = document.querySelector(".zoom__elm");
        this.zoom__info = document.querySelector(".zoom__info");
        this.zoom__area = document.querySelector(".zoom__area");
        this.zoom__origin01 = document.querySelector(".zoom__origin--01");
        this.zoom__origin02 = document.querySelector(".zoom__origin--02");
        // -----------------------------
        this.isMouseEnter = false; // エリア内フォーカスの判定用

        this.baseSize = 768; // 拡大用画像のサイズとりあえず1:1のサイズのみ
        this.areaSize = this.baseSize / 5; // 拡大エリア表示のサイズ

        this.currentScale = 1.0; // スケール初期値・現在値
        this.scaleMin = 1.0; // スケール最小値
        this.scaleMax = 3.0; // スケール最大値
        this.scaleAdd = 0.02; // マウススクロールでのスケール変化値

        this.mousewheelevent = "onwheel" in document ? "wheel" : "onmousewheel" in document ? "mousewheel" : "DOMMouseScroll"; //マウスホイールのイベント
        this.originTimer = new Timer(100); // マウスホイールのタイマー

        this.areaScale = 1.0; //表示エリアのスケール
        this.factorNum = 0.10; // イージング 係数
        this.fractionNum = 0.001; // 繰り上げ・切り下げ 端数
        // -----------------------------
        this.pointerPoint = new Point(); // マウス位置 スケール基準点を決める用
        this.downPoint = new Point(); // ダウン時の要素位置
        this.currentPoint = new Point(); // 現在位置
        this.targetPoint = new Point(); // 目標位置
        this.originPoint = new Point(0.5, 0.5); // 拡大縮小の基準点
        // -----------------------------
        this.resize();
        this.events();
        this.update();
        // -----------------------------
    }

    normalizePoint(_x, _y) {
        // elmRectとbaseSizeに合わせてマウス位置を0.0 ~ 1.0に正規化
        return new Point(
            this.fraction((_x - this.elmRect.left) / this.baseSize),
            this.fraction((_y - this.elmRect.top) / this.baseSize)
        );
    }

    normalizeScale() {
        // スケール 値最小/最大値以内に
        if (this.currentScale < this.scaleMin) this.currentScale = this.scaleMin;
        if (this.currentScale > this.scaleMax) this.currentScale = this.scaleMax;
    }

    fraction(_v) {
        // 位置端数値 繰り上げ繰り下げ
        if (_v < 0.0 + this.fractionNum) _v = 0.0;
        if (_v > 1.0 - this.fractionNum) _v = 1.0;
        return _v;
    }

    resetScale() {
        this.currentScale = 1.0;
        this.areaScale = 1.0 / this.currentScale;
    }

    events() {
        window.addEventListener("resize", this.resize.bind(this), false);

        // PCイベント
        this.zoom.addEventListener("mouseenter", () => { this.isMouseEnter = true; }, false);
        this.zoom.addEventListener("mouseleave", () => { this.isMouseEnter = false; }, false);

        document.addEventListener("mousedown", this.mousedown.bind(this), false);
        document.addEventListener("mousemove", this.mousemove.bind(this), false);
        window.addEventListener(this.mousewheelevent, this.mousewheel.bind(this), { passive: false });

        // スマホイベント
        document.addEventListener("touchstart", this.touchstart.bind(this), false);
        document.addEventListener("touchmove", this.touchmove.bind(this), { passive: false });
        document.addEventListener("gesturechange", this.gesturechange.bind(this), false);
    }

    resize() {
        this.elmRect = this.zoom__elm.getBoundingClientRect();
    }

    mousedown() {
        this.downPoint = new Point(this.currentPoint.x, this.currentPoint.y);
    }

    mousemove(e) {
        if (!this.isMouseEnter) return;

        this.pointerPoint = this.normalizePoint(e.clientX, e.clientY);

        // マウスドラッグ時の処理
        if (EventTouch.instance.isDown)
            this.addDiffPoint();
    }

    mousewheel(e) {
        // PCはマウススクロールでスケール変更
        if (!this.isMouseEnter) return;

        if (!e) e = window.event;
        e.preventDefault();

        if (EventTouch.instance.mouseDelta(e) < 0) {
            this.currentScale -= this.scaleAdd;
        } else {
            this.currentScale += this.scaleAdd;
        }

        this.normalizeScale();
        this.areaScale = 1.0 / this.currentScale;

        if (this.originTimer.isTimeOut) this.updateOrigin();

        this.originTimer.reset();
    }

    touchstart(e) {
        e = e.originalEvent ? e.originalEvent : e;

        if (e.touches.length > 1) {
            const interpolate = Point.interpolate(EventTouch.instance.touchesStartPoints[0], EventTouch.instance.touchesStartPoints[1], 0.5);
            this.pointerPoint = this.normalizePoint(interpolate.x, interpolate.y);
            if (this.originTimer.isTimeOut) {
                this.updateOrigin();
            }
            this.originTimer.reset();
        } else {
            this.downPoint = new Point(this.currentPoint.x, this.currentPoint.y);
        }
    }

    touchmove(e) {
        e.preventDefault();

        if (e.touches.length > 1) return;

        this.addDiffPoint();
    }
    gesturechange() {
        this.currentScale += (EventTouch.instance.scale - 1.0) * 0.1;
        this.normalizeScale();
        this.areaScale = 1.0 / this.currentScale;
    }

    addDiffPoint() {
        const addPoint = new Point(EventTouch.instance.diffPoint.x / this.baseSize, EventTouch.instance.diffPoint.y / this.baseSize);
        this.targetPoint = new Point(this.downPoint.x - addPoint.x, this.downPoint.y - addPoint.y);
    }

    updateOrigin() {
        // 拡大縮小の基準点設定
        const tempOriginPoint = new Point(this.originPoint.x, this.originPoint.y);

        const areaLeft = this.originPoint.x * (this.currentScale - 1.0) / this.currentScale + this.currentPoint.x * this.areaScale;
        const areaTop = this.originPoint.y * (this.currentScale - 1.0) / this.currentScale + this.currentPoint.y * this.areaScale;

        const originX = areaLeft + this.pointerPoint.x * this.areaScale;
        const originY = areaTop + this.pointerPoint.y * this.areaScale;
        this.originPoint = new Point(originX, originY);

        const diffPoint = new Point(this.originPoint.x - tempOriginPoint.x, this.originPoint.y - tempOriginPoint.y);

        diffPoint.x = diffPoint.x - (diffPoint.x * this.currentScale);
        diffPoint.y = diffPoint.y - (diffPoint.y * this.currentScale);

        this.currentPoint.x = this.targetPoint.x = (this.currentPoint.x + diffPoint.x);
        this.currentPoint.y = this.targetPoint.y = (this.currentPoint.y + diffPoint.y);
    }

    update() {
        window.requestAnimationFrame(this.update.bind(this));
        this.moveArea();
        this.easing();
        this.addStyle();
        this.updateInfo();
    }

    moveArea() {
        // originPointに合わせてドラッグ/スワイプでの移動範囲を決める
        this.areaLeftMin = this.originPoint.x - (this.originPoint.x * this.currentScale);
        this.areaRightMax = -((1.0 - this.originPoint.x) - (1.0 - this.originPoint.x) * this.currentScale);
        this.areaTopMin = this.originPoint.y - (this.originPoint.y * this.currentScale);
        this.areaBottomMax = -((1.0 - this.originPoint.y) - (1.0 - this.originPoint.y) * this.currentScale);
    }

    easing() {
        // 位置のイージング処理
        if (this.targetPoint.x <= this.areaLeftMin)
            this.targetPoint.x += (this.areaLeftMin - this.targetPoint.x) * this.factorNum;
        if (this.targetPoint.x >= this.areaRightMax)
            this.targetPoint.x += (this.areaRightMax - this.targetPoint.x) * this.factorNum;
        if (this.targetPoint.y <= this.areaTopMin)
            this.targetPoint.y += (this.areaTopMin - this.targetPoint.y) * this.factorNum;
        if (this.targetPoint.y >= this.areaBottomMax)
            this.targetPoint.y += (this.areaBottomMax - this.targetPoint.y) * this.factorNum;

        this.currentPoint.x += (this.targetPoint.x - this.currentPoint.x) * this.factorNum;
        this.currentPoint.y += (this.targetPoint.y - this.currentPoint.y) * this.factorNum;

        if (Math.abs(this.currentPoint.x - this.targetPoint.x) < this.fractionNum)
            this.currentPoint.x = this.targetPoint.x;
        if (Math.abs(this.currentPoint.y - this.targetPoint.y) < this.fractionNum)
            this.currentPoint.y = this.targetPoint.y;
    }

    addStyle() {
        // 要素にスタイルやスケール等を反映
        Object.assign(this.zoom__elm.style, {
            "transform": `translate(
      ${-this.currentPoint.x * this.baseSize}px,
      ${-this.currentPoint.y * this.baseSize}px
      )
      scale(${this.currentScale})`,
            "transform-origin": `${this.originPoint.x * this.baseSize}px ${this.originPoint.y * this.baseSize}px`
        });

        this.areaScale = 1.0 / this.currentScale;
        Object.assign(this.zoom__area.style, {
            "transform": `translate(
      ${this.currentPoint.x * this.areaSize * this.areaScale}px,
      ${this.currentPoint.y * this.areaSize * this.areaScale}px
      )
      scale(${this.areaScale})`,
            "transform-origin": `${this.originPoint.x * this.areaSize}px ${this.originPoint.y * this.areaSize}px`
        });

        Object.assign(this.zoom__origin01.style, {
            "transform": `translate(
      ${this.originPoint.x * this.baseSize}px,
      ${this.originPoint.y * this.baseSize}px
      )`
        });

        Object.assign(this.zoom__origin02.style, {
            "transform": `translate(
      ${this.originPoint.x * this.areaSize}px,
      ${this.originPoint.y * this.areaSize}px
      )`
        });
    }

    updateInfo() {
        // 確認用テキスト更新
        document.querySelector(".zoom__cp span").innerHTML = `(${this.currentPoint.x.toFixed(2)} : ${this.currentPoint.y.toFixed(2)})`;
        document.querySelector(".zoom__op span").innerHTML = `(${this.originPoint.x.toFixed(2)} : ${this.originPoint.y.toFixed(2)})`;
        document.querySelector(".zoom__scale span").innerHTML = `(${this.currentScale.toFixed(2)})`;
    }
}

/* ===========================================================
# DOMContentLoaded
=========================================================== */
window.addEventListener("DOMContentLoaded", () => {
    new EventTouch();
    new Modal();
    new Slider();
    new Zoom();
}, false);
View Compiled
Run Pen

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.1/css/all.min.css

External JavaScript

This Pen doesn't use any external JavaScript resources.