Giter VIP home page Giter VIP logo

vue-pinch-scroll-zoom's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

vue-pinch-scroll-zoom's Issues

Rewrite into standard Vue component

I could not figure out how to use this with Nuxt - the decorators with typescript just didn't want to work. So I rewrote everything into a standard Vue component. Maybe that helps someone else. (I would love to see this plugin without the need for TypeScript support /
without the need for vue-property-decorator. It's exactly what I needed and the source code did save me a ton of time! :)

Parent:

<Zoom
  ref="zoomer"
  width="1920"
  height="1080"
  :minScale="1"
  @scaling="zoomHandler"
  @dragging="zoomHandler"
  @startDrag="zoomHandler"
  @stopDrag="zoomHandler"
>
  ...
</Zoom>
methods: {
  zoomHandler(zoomData) {
    console.log(zoomData)
  },
}

Child:
Still requires pinch-scroll-zoom-axis.ts script (and TypeScript support)

<template>
  <div
    class="pinch-scroll-zoom"
    :class="componentClass"
    @mousedown="startDrag"
    @mousemove="doDrag"
    :style="componentStyle"
  >
    <div
      ref="content"
      class="pinch-scroll-zoom__content"
      :style="containerStyle"
    >
      <slot></slot>
    </div>
  </div>
</template>
<script>
  import PinchScrollZoomAxis from "../plugins/custom/zoom/pinch-scroll-zoom-axis";
  import _ from 'lodash'

  export default {
    name: "Zoom",
    data() {
      return {
        touch1: false,
        touch2: false,
        currentScale: this.scale,
        startScale: this.scale,
        axisX: new PinchScrollZoomAxis(
          this.width,
          this.originX,
          this.translateX,
          this.contentWidth
        ),
        axisY: new PinchScrollZoomAxis(
          this.height,
          this.originY,
          this.translateY,
          this.contentHeight
        ),
        throttleDoDrag: _.throttle(this.doDragEvent.bind(this), this.throttleDelay),
        stopScaling: _.debounce(this.doStopScalingEvent.bind(this), 200),
        zoomIn: false,
        zoomOut: false,
        stopDragListener: false,
        startDragListener: false,
        draggingListener: false,
        scalingListener: false,
      };
    },

    props: {
      contentWidth: {
        type: Number | undefined
      },
      contentHeight: {
        type: Number | undefined
      },
      width: {
        required: true,
        type: Number
      },
      height: {
        required: true,
        type: Number
      },
      originX: {
        type: Number | undefined
      },
      originY: {
        type: Number | undefined
      },
      translateX: {
        type: Number,
        default: 0
      },
      translateY: {
        type: Number,
        default: 0
      },
      scale: {
        type: Number,
        default: 1
      },
      throttleDelay: {
        type: Number,
        default: 25
      },
      within: {
        type: Boolean,
        default: true
      },
      minScale: {
        type: Number,
        default: 0.3
      },
      maxScale: {
        type: Number,
        default: 5
      },
      wheelVelocity: {
        type: Number,
        default: 0.001
      },
      draggable: {
        type: Boolean,
        default: true
      },
    },

    computed: {
      componentStyle() {
        return {
          width: `${this.width}px`,
          height: `${this.height}px`,
        }
      },
      componentClass() {
        return {
          "pinch-scroll-zoom--zoom-out": this.zoomOut,
          "pinch-scroll-zoom--zoom-in": this.zoomIn,
        }
      },
      containerStyle() {
        const x = `${this.axisX.point}px`;
        const y = `${this.axisY.point}px`;
        const translate = `translate(${x}, ${y}) scale(${this.currentScale})`;
        const transformOrigin = `${this.axisX.origin}px ${this.axisY.origin}px`;

        return {
          transform: translate,
          "transform-origin": transformOrigin,
        }
      },
    },

    watch: {

      scale(val) {
        this.submitScale(val)
      },

      translateX(val) {
        this.axisX.setPoint(val)
      },

      translateY(val) {
        this.axisY.setPoint(val)
      },

      originX(val) {
        this.axisX.setOrigin(val)
      },

      originY(val) {
        this.axisY.setOrigin(val)
      },

      within(val) {
        this.checkWithin()
      },

    },

    methods: {

      setData(data) {
        this.currentScale = data.scale;
        this.axisX.setPoint(data.translateX);
        this.axisY.setPoint(data.translateY);
        this.axisX.setOrigin(data.originX);
        this.axisY.setOrigin(data.originY);
      },

      getEmitData() {
        return {
          x: this.axisX.point,
          y: this.axisY.point,
          scale: this.currentScale,
          originX: this.axisX.origin,
          originY: this.axisY.origin,
          translateX: this.axisX.point,
          translateY: this.axisY.point,
        }
      },

      stopDrag() {
        this.touch1 = false;
        this.touch2 = false;
        this.zoomIn = false;
        this.zoomOut = false;
        if (this.stopDragListener) {
          this.$emit("stopDrag", this.getEmitData());
        }
      },

      startDrag(touchEvent) {
        if (!this.draggable) return;

        if (!touchEvent.touches) {
          touchEvent.touches = [
            {
              clientX: touchEvent.clientX,
              clientY: touchEvent.clientY,
            },
          ];
        }
        if (touchEvent.touches.length == 0) {
          this.stopDrag();
          return;
        }
        const clientX1 = this.getBoundingTouchClientX(touchEvent.touches[0]);
        const clientY1 = this.getBoundingTouchClientY(touchEvent.touches[0]);
        if (touchEvent.touches.length > 1) {
          this.touch1 = true;
          this.touch2 = true;
          this.startScale = this.currentScale;

          const clientX2 = this.getBoundingTouchClientX(touchEvent.touches[1]);
          const clientY2 = this.getBoundingTouchClientY(touchEvent.touches[1]);

          this.axisX.pinch(clientX1, clientX2, this.currentScale);
          this.axisY.pinch(clientY1, clientY2, this.currentScale);
        } else {
          this.touch1 = true;
          this.touch2 = false;
          this.axisX.touch(clientX1);
          this.axisY.touch(clientY1);
        }

        if (this.startDragListener) {
          this.$emit("startDrag", this.getEmitData());
        }
      },

      doDrag(touchEvent) {
        if (!this.draggable) return;

        this.throttleDoDrag(touchEvent);
      },

      doStopScalingEvent() {
        this.zoomIn = false;
        this.zoomOut = false;
      },


      doDragEvent(touchEvent) {
        if (!this.touch1 && !this.touch2) return;
        if (!touchEvent.touches) {
          touchEvent.touches = [
            {
              clientX: touchEvent.clientX,
              clientY: touchEvent.clientY,
            },
          ];
        }
        if (touchEvent.touches.length === 0) return;

        if (this.touch1 && this.touch2 && touchEvent.touches.length === 1)
          this.startDrag(touchEvent);

        if (!this.touch1 || (!this.touch2 && touchEvent.touches.length === 2))
          this.startDrag(touchEvent);

        if (this.touch1 && this.touch2) {
          this.axisX.dragPinch(
            this.getBoundingTouchClientX(touchEvent.touches[0]),
            this.getBoundingTouchClientX(touchEvent.touches[1])
          );
          this.axisY.dragPinch(
            this.getBoundingTouchClientY(touchEvent.touches[0]),
            this.getBoundingTouchClientY(touchEvent.touches[1])
          );
        } else {
          this.axisX.dragTouch(this.getBoundingTouchClientX(touchEvent.touches[0]));
          this.axisY.dragTouch(this.getBoundingTouchClientY(touchEvent.touches[0]));
        }

        this.doScale(touchEvent);
        this.submitDrag();
      },

      getBoundingTouchClientX(touch) {
        return touch.clientX - this.$el.getBoundingClientRect().left;
      },

      getBoundingTouchClientY(touch) {
        return touch.clientY - this.$el.getBoundingClientRect().top;
      },

      submitDrag() {
        if (this.draggingListener) {
          this.$emit("dragging", this.getEmitData());
        }
      },

      getDistance(x1, y1, x2, y2) {
        const sqrDistance = (x1 - x2) ** 2 + (y1 - y2) ** 2;
        const distance = Math.sqrt(sqrDistance);
        return distance;
      },

      doScale(touchEvent) {
        if (touchEvent.touches.length < 2 || !this.touch1 || !this.touch2) {
          this.checkWithin();
          return;
        }

        const touch1 = touchEvent.touches[0];
        const touch2 = touchEvent.touches[1];

        const distance = this.getDistance(
          this.getBoundingTouchClientX(touch1),
          this.getBoundingTouchClientY(touch1),
          this.getBoundingTouchClientX(touch2),
          this.getBoundingTouchClientY(touch2)
        );

        const startDistance = this.getDistance(
          this.axisX.start1,
          this.axisY.start1,
          this.axisX.start2,
          this.axisY.start2
        );

        const scale = this.startScale * (distance / startDistance);
        this.submitScale(scale);
      },

      submitScale(scale) {
        if (
          (scale >= this.minScale || this.currentScale < scale) &&
          (scale <= this.maxScale || this.currentScale > scale)
        ) {
          if (this.currentScale != scale) {
            this.zoomIn = this.currentScale < scale;
            this.zoomOut = this.currentScale > scale;
            this.currentScale = scale;
            this.stopScaling();
          }
        }
        this.checkWithin();
        if (this.scalingListener) {
          this.$emit("scaling", this.getEmitData());
        }
      },

      doWheelScale(event) {
        event.preventDefault();
        const clientX = this.getBoundingTouchClientX(event);
        const clientY = this.getBoundingTouchClientY(event);
        this.axisX.pinch(clientX, clientX, this.currentScale);
        this.axisY.pinch(clientY, clientY, this.currentScale);

        const factor = 1 - event.deltaY * this.wheelVelocity;
        const scale = this.currentScale * factor;

        this.submitScale(scale);
      },

      checkWithin(event) {
        if (!this.within) {
          return;
        }

        this.axisY.checkAndResetToWithin(this.currentScale);
        this.axisX.checkAndResetToWithin(this.currentScale);
      },


    },

    mounted () {
      window.addEventListener("mouseup", this.stopDrag);
      this.$el.addEventListener("touchstart", this.startDrag);
      this.$el.addEventListener("touchmove", this.doDrag);
      this.$el.addEventListener("wheel", this.doWheelScale);

      this.stopDragListener = !!this.$listeners.stopDrag;
      this.startDragListener = !!this.$listeners.startDrag;
      this.draggingListener = !!this.$listeners.dragging;
      this.scalingListener = !!this.$listeners.scaling;
    },

    beforeDestroy() {
      window.removeEventListener("mouseup", this.stopDrag);
      this.$el.removeEventListener("touchstart", this.startDrag);
      this.$el.removeEventListener("touchmove", this.doDrag);
      this.$el.removeEventListener("wheel", this.doWheelScale);
    },

  }
</script>
<style lang="scss">
.pinch-scroll-zoom {
  position: relative;
  touch-action: none;
  user-select: none;
  user-zoom: none;
  overflow: hidden;
  :active {
    cursor: all-scroll;
  }

  &--zoom-in {
    cursor: zoom-in;
  }

  &--zoom-out {
    cursor: zoom-out;
  }

  &__content {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    img {
      -webkit-user-drag: none;
      -khtml-user-drag: none;
      -moz-user-drag: none;
      -o-user-drag: none;
    }
  }
}
</style>

Off positioned cursor/touch origin

Hi,

First of all, thanks for the neat plugin. You did a great job!

I noticed an annoying bug when the component is used in the right side of the window and you start scrolling to zoom which results in off positioned originX and originY.

The possible solution could be using a ref for div.pinch-scroll-zoom and calculating the refs' absolute position with .getBoundingClientRect().left for horizontal axis and .getBoundingClientRect().top for vertical axis, then extracting these distances from originX and originY accordingly.

Docs : Miss the needed dependency vue-property-decorator

Hi,

I got this error while trying to use your package:

This dependency was not found:

* vue-property-decorator in ./node_modules/@coddicat/vue-pinch-scroll-zoom/lib/index.esm.js

I'm adapting the package so it doesn't use vue-property-decorator but I think it's worth the mentioning in the documentation :)

Thanks !

The `setData` method does not respect `within` props

Hi, thanks for making this awesome library.

I've been struggling to use the setData method with within props.
If setData method gets translateX or translateY value that larger than container size, within feature does not work.

reproduction

if u press the test button, u will see what I mean

got error about loaders

./node_modules/.pnpm/@[email protected]/node_modules/@coddicat/vue-pinch-scroll-zoom/dist/pinch-scroll-zoom.js 2094:22
Module parse failed: Unexpected token (2094:22)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
|       function jn(n, t) {
|         var e = typeof n;
>         return t = t ?? v , !!t && (e == "number" || e != "symbol" && ts.test(n)) && n > -1 && n % 1 == 0 && n < t;
|       }
|       function an(n, t, e) {

I used "vue": "^3.0.0" with webpack,

import "@coddicat/vue-pinch-scroll-zoom/dist/style.css";
import PinchScrollZoom, {
  PinchScrollZoomExposed,
} from "@coddicat/vue-pinch-scroll-zoom";

when I running my project, I got error above

I try to add

  plugins: [
    ["@babel/plugin-proposal-nullish-coalescing-operator"],
    ["@babel/plugin-proposal-optional-chaining"],
  ],

in babel.config.js but still not work

Looking for a way to programatically change the scale and respecting the context/within parameter (zoom/unzoom buttons)

Hi,

I'm desperate in trying to find a way to zoom in and zoom out (while respecting the max and min scale set) by pushing on a + ou - button.

Right now I'm using this:

zoomer.value?.setData({
    scale: newScale,
    originX: state.originX,
    originY: state.originY,
    translateX: state.translateX,
    translateY: state.translateY,
  })

newScale is set with a value withing my min and max scale and it works but not in a logic way.

I'm trying to zoom in the middle and unzoom from the middle, just like if I mousewheeled from the center but on the click on a + or - button. Also, with the method I use now, if I unzoom but I was watching the upper part of content, the content gets out of bound as the scale reduce, until I drag the content, then the "within" context is respected and the content gets back to a good position.

One solution could be to add accessible method to allow setting the scale (and scale from middle), but it would then process like a mousewheel event for content positions?

As last time, I'm ok to contribute or sponsor this plugin, with buymeacoffee or anything else. Let me know! ๐Ÿ™‚

I can also provide video if needed.

Thank you!

Error when trying to change translate-x and translate-y props

When the translate-x or translate-y props are updated I get the following error in the console:

image

Here's my code:

<template>

  <PinchScrollZoom
    ref="zoom"
    :width="screenWidth"
    :height="screenHeight"
    :contentWidth="contentWidth"
    :contentHeight="contentHeight"
    :scale="currentScale"
    :minScale="initScale"
    :maxScale="initScale * 10"
    :origin-x="originX"
    :origin-y="originY"
    :translate-x="translateX"
    :translate-y="translateY"
    @scaling="onScale"
  >
    <div ref="scrollContent" :style="{ width: contentWidth, height: contentHeight }" @click="myEvent"><img src="/lanetest.png" /></div>
  </PinchScrollZoom>

</template>

<script>

import PinchScrollZoom from '@coddicat/vue-pinch-scroll-zoom'

export default {

  components: {
    PinchScrollZoom
  },

  data () {
    return {
      contentWidth: 1102,
      contentHeight: 1966,
      currentScale: 0,
      originX: 0,
      originY: 0,
      translateX: 0,
      translateY: 0
    }
  },

  computed: {

    screenHeight () {
      return this.$q.screen.height - 50
    },
    screenWidth () {
      return this.$q.screen.width
    },
    initScale () {
      // Calculate the initial scale to fit the image in the screen
      return Math.max(
        this.screenWidth / this.contentWidth,
        this.screenHeight / this.contentHeight
      )
    }

  },

  methods: {

    myEvent (e) {
      console.log(e.offsetX, e.offsetY)
      this.translateX = 39
      this.translateY = -1009
      this.currentScale = this.initScale * 4
    },

    onScale (e) {
      console.log(e)
      this.currentScale = e.scale
    }

  },

  mounted () {
    this.currentScale = this.initScale
  }

}
</script>

<style>
</style>

Tested in a brand new Quasar/Vite project with the above in App.vue.

Unable to resolve dependency trees

I couldn't install this with my vue-cli 3 project. Below is the error code.

npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! npm ERR! While resolving: [email protected] npm ERR! Found: [email protected] npm ERR! node_modules/vue npm ERR! vue@"^3.2.13" from the root project npm ERR! npm ERR! Could not resolve dependency: npm ERR! peer vue@"^2.6.11" from @coddicat/[email protected] npm ERR! node_modules/@coddicat/vue-pinch-scroll-zoom npm ERR! @coddicat/vue-pinch-scroll-zoom@"*" from the root project npm ERR! npm ERR! Fix the upstream dependency conflict, or retry npm ERR! this command with --force, or --legacy-peer-deps npm ERR! to accept an incorrect (and potentially broken) dependency resolution.

Full viewport size

Is it possible to set the width and height props to match the viewport width and height and have within enabled?

My use case is a responsive fullscreen modal, with a image.

Failed to resolve entry for package...

I get the following error when I try to use this in my app:

Failed to resolve entry for package "@coddicat/vue-pinch-scroll-zoom". The package may have incorrect main/module/exports specified in its package.json: Failed to resolve entry for package "@coddicat/vue-pinch-scroll-zoom". The package may have incorrect main/module/exports specified in its package.json.

Am I missing something obvious? It's a vue 3 / Quasar 2 app and the component I'm importing into used the options API if that's relevant?

This occurs even in this simple component:

<template>

  <PinchScrollZoom>
    <img src="/test.png"/>
  </PinchScrollZoom>

</template>


<script setup>
import PinchScrollZoom from '@coddicat/vue-pinch-scroll-zoom'
</script>

Any help greatly appreciated as this looks like just what I've been looking for to replace PinchZoom.js! :)

How to disable scrolling and panning

Hi,

I'm trying to find a way how to disable all scrolling and panning, while keeping the current transformation. Is that possible. And if so, can you tell me how to do that?

Impossible to get back to scale = 1 with mousewheel if min-scale = 1

Hi,

I have set min-scale to 1 and max-scale to 3. I can zoom with the mouse wheel and it works well, but then when I unzoom with the mouse wheel, it can't get back precisely to the 1.0 scale (min-value), it remains a little bit bigger.

If I zoom and unzoom many times with mouse wheel I can get close to the 1.0 scale but never exactly it. Playing with the wheel-velocity helps a little and allows me to get closer ton min-scale, but does not fix the issue.

It's problematic because it leaves room for a vertical scroll even when the content is same height as the viewer.

Thank you for the great package, it's very useful. If you have a buymeacoffeelink I'd send a lil something for sure.

I have other less important issues that I might post here soon, I try to gather more informations.

Have a good day!

Set width and height other than px

First of all, great component!

I wondering if it is possible to use values for width and height, other than pixels. For example %, vh etc.

<PinchScrollZoom
      ref="zoomer"
      within
      centred
      key-actions
      width="100%"
      height="100vh"
      :min-scale="0.5"
      :max-scale="15"
      @scaling="(e) => onEvent('scaling', e)"
      @startDrag="(e) => onEvent('startDrag', e)"
      @stopDrag="(e) => onEvent('stopDrag', e)"
      @dragging="(e) => onEvent('dragging', e)"
>
    <img src="..." />
</PinchScrollZoom>

Width and Height are not reactive

I'm using the Vue3 version and it looks like the width and height values are not reactive, so for example, I can't dynamically resize the zoomer in a responsive design app. I guess I can understand this isn't an easy undertaking, but thought I'd save somebody a few hours trying to make it work.

 <PinchScrollZoom
    :width="item.slideSizeWidth"  // dynamically changing item.slideSizeWidth has no effect !!!
    :height="item.slideSizeHeight"

If one were to implement this feature, forcing a reset whenever the width or height changed would be a fine simplification.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.