diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 0f33f50a..cd1c69e8 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -19,19 +19,44 @@ the expected behavior & use case, where they’ll remain closed until sufficient [e.g. :+1: reactions](https://help.github.com/articles/about-discussions-in-issues-and-pull-requests/), has been [shown by the community](https://github.com/timmywil/panzoom/issues?q=label%3A%22votes+needed%22+sort%3Areactions-%2B1-desc). Before submitting a request, please search for similar ones in the -[closed issues](https://github.com/timmywil/panzoom/issues?q=is%3Aissue+is%3Aclosed+label%3Aenhancement). +[closed issues](https://github.com/timmywil/panzoom/issues?q=is%3Aissue+is%3Aclosed+label%3Afeature). -I got this convention from [lodash](https://github.com/lodash/lodash). It helps keep the issues list as empty as possible. +I got this convention from [lodash](https://github.com/lodash/lodash). It helps keep the open issues list uncluttered. ## Pull Requests -For additions or bug fixes you should only need to modify `panzoom.ts`. Include -updated unit tests in the `test` directory as part of your pull request. Don’t -worry about regenerating the built files or docs. +For additions or bug fixes you should only need to modify files in `src/`. Include +updated unit tests in the `test` directory or an updated/added demo in the `demo` directory as part of your pull request. Don’t worry about regenerating the built files. -Before running the unit tests you’ll need to install, `yarn` or `npm i`, -[development dependencies](https://docs.npmjs.com/files/package.json#devdependencies). -Run unit tests from the command-line via `yarn test` or `npm test`. +## Editing documentation + +### Do not edit below the "Documentation" header in the README + +Edit the comments and type declarations in the `src/` folder, or the markdown files in the `tasks/` folder. + +The documentation is auto-generated using a combination of [typedoc](https://typedoc.org/) and a hand-rolled script (`tasks/docs.js`). First, typedoc generates markdown from the TypeScript files into the `docs/` folder, which is ignored by git, and then `tasks/docs.js` concatenates those files and cleans them up for presentation in the README. + +## Testing + +Tests are written with [mocha](https://mochajs.org/) and [Node's official assert module](https://nodejs.org/api/assert.html#assert_assert). + +Here are the npm scripts that run tests: + +```bash +$ yarn test # Lints and runs the unit tests +$ yarn test:unit # Runs the unit tests +$ yarn test:watch # Watches files and runs the unit tests on file save +``` + +## Building + +The `dist/` folder is ignored on master and included in releases. To build, first make sure dependencies are installed (`yarn` or `npm i`) and run the following: + +```bash +$ yarn build # or npm run build +``` + +This is usually unnecessary as `yarn start` will rebuild automatically as you change files. ## Coding Guidelines @@ -88,18 +113,6 @@ Run the following after staging files: $ yarn commit ``` -## Testing - -Tests are written with [mocha](https://mochajs.org/) and [Node's official assert module](https://nodejs.org/api/assert.html#assert_assert). - -Here are the npm scripts that run tests: - -```bash -$ yarn test # Lints and runs the unit tests -$ yarn test:unit # Runs the unit tests -$ yarn test:watch # Watches files and runs the unit tests on file save -``` - ## Debugging in VS Code First run `yarn start`, then run VS Code Debug with the following config to enable breakpoints: @@ -110,7 +123,7 @@ First run `yarn start`, then run VS Code Debug with the following config to enab "request": "launch", "name": "Launch Chrome", "url": "http://localhost:8080", - "webRoot": "${workspaceRoot}", + "webRoot": "${workspaceFolder}", "sourceMaps": true } ``` diff --git a/README.md b/README.md index 3b6fda3b..9d29b098 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Panzoom -[![Build Status](https://travis-ci.org/timmywil/panzoom.png?branch=master)](https://travis-ci.org/timmywil/panzoom) +[![Build Status](https://travis-ci.org/timmywil/panzoom.png?branch=master)](https://travis-ci.org/timmywil/panzoom) [![Greenkeeper badge](https://badges.greenkeeper.io/timmywil/panzoom.svg)](https://greenkeeper.io/) **[Examples](https://timmywil.com/panzoom/demo/)** @@ -10,12 +10,10 @@ Have a look at the [GitHub project](https://github.com/timmywil/panzoom/projects --- -Panzoom is a small library to add panning and zooming functionality to an element. +Panzoom is a small library (~3kb gzipped) to add panning and zooming functionality to an element. Rather than using absolute positioning or setting width and height, Panzoom uses CSS transforms to take advantage of hardware/GPU acceleration in the browser, which means the element can be _anything_: an image, a video, an iframe, a canvas, text, WHATEVER. -panzoom.min.js, included in this repo, is compressed with [uglifyjs](https://github.com/mishoo/UglifyJS). - -For common support questions, see [the FAQ](https://github.com/timmywil/panzoom#faq) at the bottom. +For common support questions, see [the FAQ](https://github.com/timmywil/panzoom#faq). ## Browser support @@ -35,36 +33,63 @@ In IE11, CSS animations/transitions do not work on SVG elements, at least for th One could implement transitions manually in IE11 using the `setTransform` option and integrating a tweening library for javascript animations (such as [tween.js](https://www.createjs.com/#!/TweenJS)). -## Loading Panzoom +## Installing + +With npm: + +```bash +$ npm install --save @panzoom/panzoom +``` + +With yarn: + +```bash +$ yarn add @panzoom/panzoom +``` Panzoom uses [UMD](https://github.com/umdjs/umd) and can be loaded a lot of ways. With ES6 imports: ```js -import Panzoom from 'panzoom' +import Panzoom from '@panzoom/panzoom' +``` + +With commonjs or browserify: + +``` +const Panzoom = require('@panzoom/panzoom') ``` -With AMD loader in an anonymous module: +With an AMD loader in an anonymous module: ```js -define(['panzoom'], function(Panzoom) { +define(['@panzoom/panzoom'], function(Panzoom) { Panzoom('.panzoom') }) ``` -With script tags: +With a script tag: ```html ``` -## Initialization +## Usage ```js -const panzoom = Panzoom('.panzoom', { +const elem = document.getElementById('panzoom-element') +const panzoom = Panzoom(elem, { maxScale: 5 }) +panzoom.pan(10, 10) +panzoom.zoom(2, { animate: true }) + +// Panning and pinch zooming are bound automatically (unless disablePan is true). +// There are several available methods for zooming +// that can be bound on button clicks or mousewheel. +button.addEventListener('click', panzoom.zoomIn) +elem.parentElement.addEventListener('wheel', panzoom.zoomWithWheel) ``` ## FAQ @@ -75,24 +100,22 @@ const panzoom = Panzoom('.panzoom', { - HTML elements default to '50% 50%'. - SVG elements default to '0 0'. -2\. I am using Panzoom with an `` tag. It zooms but does not pan. [example](https://codepen.io/timmywil/pen/qNpykA) +2\. I am using Panzoom with an `` tag and it's not working. What's wrong? Object elements can eat up events, making it so they never reach Panzoom. To fix this, disable pointer events (`pointer-events: none`) on the `` tag and call Panzoom using a wrapper. 3\. My links aren't working! How do I enable an anchor within a panzoom element? Add class `options.clickableClass` (default is `"clickable"`) to whatever element you want to be clickable. Panzoom will check for this class before handling the event. -You can also call `event.stopImmediatePropagation()` in an event handler on the clickable element. +Alternatively, call `event.stopImmediatePropagation()` in an event handler on the clickable element. --- ## Documentation -### Default export - ▸ **Panzoom**(`elem`: `HTMLElement` | `SVGElement`, `options?`: [PanzoomOptions](#PanzoomOptions)): _[PanzoomObject](#PanzoomObject)_ -_Defined in [panzoom.ts:38](https://github.com/timmywil/panzoom/blob/213f0df/src/panzoom.ts#L38)_ +_Defined in [panzoom.ts:39](https://github.com/timmywil/panzoom/blob/b4b8329/src/panzoom.ts#L39)_ **Parameters:** @@ -103,112 +126,6 @@ _Defined in [panzoom.ts:38](https://github.com/timmywil/panzoom/blob/213f0df/src **Returns:** _[PanzoomObject](#PanzoomObject)_ -### `Const` defaultOptions - -_Defined in [panzoom.ts:18](https://github.com/timmywil/panzoom/blob/213f0df/src/panzoom.ts#L18)_ - -### animate - -• **animate**: _false_ = false - -_Defined in [panzoom.ts:19](https://github.com/timmywil/panzoom/blob/213f0df/src/panzoom.ts#L19)_ - -### clickableClass - -• **clickableClass**: _string_ = "clickable" - -_Defined in [panzoom.ts:20](https://github.com/timmywil/panzoom/blob/213f0df/src/panzoom.ts#L20)_ - -### cursor - -• **cursor**: _string_ = "move" - -_Defined in [panzoom.ts:21](https://github.com/timmywil/panzoom/blob/213f0df/src/panzoom.ts#L21)_ - -### disablePan - -• **disablePan**: _false_ = false - -_Defined in [panzoom.ts:22](https://github.com/timmywil/panzoom/blob/213f0df/src/panzoom.ts#L22)_ - -### disableXAxis - -• **disableXAxis**: _false_ = false - -_Defined in [panzoom.ts:24](https://github.com/timmywil/panzoom/blob/213f0df/src/panzoom.ts#L24)_ - -### disableYAxis - -• **disableYAxis**: _false_ = false - -_Defined in [panzoom.ts:25](https://github.com/timmywil/panzoom/blob/213f0df/src/panzoom.ts#L25)_ - -### disableZoom - -• **disableZoom**: _false_ = false - -_Defined in [panzoom.ts:23](https://github.com/timmywil/panzoom/blob/213f0df/src/panzoom.ts#L23)_ - -### duration - -• **duration**: _number_ = 200 - -_Defined in [panzoom.ts:26](https://github.com/timmywil/panzoom/blob/213f0df/src/panzoom.ts#L26)_ - -### easing - -• **easing**: _string_ = "ease-in-out" - -_Defined in [panzoom.ts:27](https://github.com/timmywil/panzoom/blob/213f0df/src/panzoom.ts#L27)_ - -### maxScale - -• **maxScale**: _number_ = 4 - -_Defined in [panzoom.ts:28](https://github.com/timmywil/panzoom/blob/213f0df/src/panzoom.ts#L28)_ - -### minScale - -• **minScale**: _number_ = 0.125 - -_Defined in [panzoom.ts:29](https://github.com/timmywil/panzoom/blob/213f0df/src/panzoom.ts#L29)_ - -### relative - -• **relative**: _false_ = false - -_Defined in [panzoom.ts:30](https://github.com/timmywil/panzoom/blob/213f0df/src/panzoom.ts#L30)_ - -### setTransform - -• **setTransform**: _`setTransform`_ - -_Defined in [panzoom.ts:31](https://github.com/timmywil/panzoom/blob/213f0df/src/panzoom.ts#L31)_ - -### startScale - -• **startScale**: _number_ = 1 - -_Defined in [panzoom.ts:34](https://github.com/timmywil/panzoom/blob/213f0df/src/panzoom.ts#L34)_ - -### startX - -• **startX**: _number_ = 0 - -_Defined in [panzoom.ts:32](https://github.com/timmywil/panzoom/blob/213f0df/src/panzoom.ts#L32)_ - -### startY - -• **startY**: _number_ = 0 - -_Defined in [panzoom.ts:33](https://github.com/timmywil/panzoom/blob/213f0df/src/panzoom.ts#L33)_ - -### step - -• **step**: _number_ = 0.3 - -_Defined in [panzoom.ts:35](https://github.com/timmywil/panzoom/blob/213f0df/src/panzoom.ts#L35)_ - ## `PanzoomOptions` Includes `MiscOptions`, `PanOptions`, and `ZoomOptions` @@ -217,52 +134,52 @@ Includes `MiscOptions`, `PanOptions`, and `ZoomOptions` ## `MiscOptions` -### `Optional` animate +### animate -• **animate**? : _boolean_ +• **animate**? : _boolean_ (Default: **false**) -_Defined in [types.ts:5](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L5)_ +_Defined in [types.ts:5](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L5)_ Whether to animate transitions --- -### `Optional` clickableClass +### clickableClass -• **clickableClass**? : _string_ +• **clickableClass**? : _string_ (Default: **"clickable"**) -_Defined in [types.ts:10](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L10)_ +_Defined in [types.ts:10](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L10)_ Add this class to any element within the panzoom element that you want to be clickable and not initiate the drag --- -### `Optional` duration +### duration -• **duration**? : _number_ +• **duration**? : _number_ (Default: **200**) -_Defined in [types.ts:12](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L12)_ +_Defined in [types.ts:12](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L12)_ Duration of the transition (ms) --- -### `Optional` easing +### easing -• **easing**? : _string_ +• **easing**? : _string_ (Default: **"ease-in-out"**) -_Defined in [types.ts:14](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L14)_ +_Defined in [types.ts:14](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L14)_ CSS Easing used for transitions --- -### `Optional` origin +### origin • **origin**? : _string_ -_Defined in [types.ts:28](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L28)_ +_Defined in [types.ts:28](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L28)_ **Change this at your own risk.** The `transform-origin` is the origin from which transforms are applied. @@ -278,16 +195,17 @@ And again, changing this for SVG in IE doesn't work at all. --- -### `Optional` setTransform +### setTransform • **setTransform**? : _`setTransform`_ -_Defined in [types.ts:45](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L45)_ +_Defined in [types.ts:46](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L46)_ -Override the transform setter +Override the transform setter. This is exposed mostly so the user could set other parts of a transform aside from scale and translate. +Default is defined in src/css.ts. ```js // This example always sets a rotation @@ -301,29 +219,43 @@ Panzoom(elem, { --- -### `Optional` startScale +### silent + +• **silent**? : _boolean_ -• **startScale**? : _number_ +_Defined in [types.ts:48](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L48)_ -_Defined in [types.ts:49](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L49)_ +Silence all events --- -### `Optional` startX +### startScale -• **startX**? : _number_ +• **startScale**? : _number_ (Default: **1**) -_Defined in [types.ts:47](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L47)_ +_Defined in [types.ts:54](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L54)_ -Values used to set the beginning transform +Scale used to set the beginning transform --- -### `Optional` startY +### startX + +• **startX**? : _number_ (Default: **0**) -• **startY**? : _number_ +_Defined in [types.ts:50](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L50)_ -_Defined in [types.ts:48](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L48)_ +X Value used to set the beginning transform + +--- + +### startY + +• **startY**? : _number_ (Default: **0**) + +_Defined in [types.ts:52](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L52)_ + +Y Value used to set the beginning transform --- @@ -331,11 +263,11 @@ _Defined in [types.ts:48](https://github.com/timmywil/panzoom/blob/213f0df/src/t Includes `MiscOptions` -### `Optional` contain +### contain • **contain**? : _"inside" | "outside"_ -_Defined in [types.ts:66](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L66)_ +_Defined in [types.ts:71](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L71)_ Contain the panzoom element either inside or outside the parent. @@ -349,61 +281,61 @@ empty space around the element will be shown. --- -### `Optional` cursor +### cursor -• **cursor**? : _string_ +• **cursor**? : _string_ (Default: **"move"**) -_Defined in [types.ts:68](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L68)_ +_Defined in [types.ts:73](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L73)_ The cursor style to set on the panzoom element --- -### `Optional` disablePan +### disablePan -• **disablePan**? : _boolean_ +• **disablePan**? : _boolean_ (Default: **false**) -_Defined in [types.ts:70](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L70)_ +_Defined in [types.ts:75](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L75)_ Disable panning functionality. Note: disablePan also disables focal point zooming --- -### `Optional` disableXAxis +### disableXAxis -• **disableXAxis**? : _boolean_ +• **disableXAxis**? : _boolean_ (Default: **false**) -_Defined in [types.ts:72](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L72)_ +_Defined in [types.ts:77](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L77)_ Pan only on the Y axis --- -### `Optional` disableYAxis +### disableYAxis -• **disableYAxis**? : _boolean_ +• **disableYAxis**? : _boolean_ (Default: **false**) -_Defined in [types.ts:74](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L74)_ +_Defined in [types.ts:79](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L79)_ Pan only on the X axis --- -### `Optional` panOnlyWhenZoomed +### panOnlyWhenZoomed -• **panOnlyWhenZoomed**? : _boolean_ +• **panOnlyWhenZoomed**? : _boolean_ (Default: **false**) -_Defined in [types.ts:78](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L78)_ +_Defined in [types.ts:83](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L83)_ Disable panning while the scale is equal to the starting value --- -### `Optional` relative +### relative -• **relative**? : _boolean_ +• **relative**? : _boolean_ (Default: **false**) -_Defined in [types.ts:76](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L76)_ +_Defined in [types.ts:81](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L81)_ When passing x and y values to .pan(), treat the values as relative to their current values @@ -413,21 +345,21 @@ When passing x and y values to .pan(), treat the values as relative to their cur Includes `MiscOptions` -### `Optional` disableZoom +### disableZoom -• **disableZoom**? : _boolean_ +• **disableZoom**? : _boolean_ (Default: **false**) -_Defined in [types.ts:83](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L83)_ +_Defined in [types.ts:88](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L88)_ Disable zooming functionality --- -### `Optional` focal +### focal • **focal**? : _object_ -_Defined in [types.ts:90](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L90)_ +_Defined in [types.ts:95](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L95)_ Zoom to the given point on the panzoom element. This point is expected to be relative to @@ -442,33 +374,33 @@ to the parent dimensions. --- -### `Optional` maxScale +### maxScale -• **maxScale**? : _number_ +• **maxScale**? : _number_ (Default: **4**) -_Defined in [types.ts:94](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L94)_ +_Defined in [types.ts:99](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L99)_ The maximum scale when zooming --- -### `Optional` minScale +### minScale -• **minScale**? : _number_ +• **minScale**? : _number_ (Default: **0.125**) -_Defined in [types.ts:92](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L92)_ +_Defined in [types.ts:97](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L97)_ The minimum scale when zooming --- -### `Optional` step +### step -• **step**? : _number_ +• **step**? : _number_ (Default: **0.3**) -_Defined in [types.ts:96](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L96)_ +_Defined in [types.ts:101](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L101)_ -The step affects the rate of zooming with a mouse wheel, pinching, or range element +The step affects zoom calculation when zooming with a mouse wheel, when pinch zooming, or when using zoomIn/zoomOut --- @@ -476,11 +408,11 @@ The step affects the rate of zooming with a mouse wheel, pinching, or range elem These methods are available after initializing Panzoom -### getOptions +### getOptions() • **getOptions**: _function_ -_Defined in [types.ts:117](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L117)_ +_Defined in [types.ts:122](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L122)_ Returns a _copy_ of the current options object @@ -490,11 +422,11 @@ Returns a _copy_ of the current options object --- -### getPan +### getPan() • **getPan**: _function_ -_Defined in [types.ts:113](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L113)_ +_Defined in [types.ts:118](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L118)_ Get the current x/y translation @@ -508,11 +440,11 @@ Get the current x/y translation --- -### getScale +### getScale() • **getScale**: _function_ -_Defined in [types.ts:115](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L115)_ +_Defined in [types.ts:120](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L120)_ Get the current scale @@ -522,11 +454,11 @@ Get the current scale --- -### pan +### pan() • **pan**: _function_ -_Defined in [types.ts:128](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L128)_ +_Defined in [types.ts:133](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L133)_ Pan the Panzoom element to the given x and y coordinates @@ -539,28 +471,33 @@ panzoom.pan(10, 10, { relative: true }) #### Signature with return type: -▸ (`x`: number | string, `y`: number | string, `panOptions?`: PanOptions): _[CurrentValues](#CurrentValues)_ +▸ (`x`: number | string, `y`: number | string, `panOptions?`: [PanOptions](#PanOptions)): _[CurrentValues](#CurrentValues)_ **Parameters:** -| Name | Type | -| ------------- | ---------------- | -| `x` | number \| string | -| `y` | number \| string | -| `panOptions?` | PanOptions | +| Name | Type | +| ------------- | ------------------------- | +| `x` | number \| string | +| `y` | number \| string | +| `panOptions?` | [PanOptions](#PanOptions) | --- -### reset +### reset() • **reset**: _function_ -_Defined in [types.ts:134](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L134)_ +_Defined in [types.ts:144](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L144)_ Reset the pan and zoom to startX, startY, and startScale. Animates by default, ignoring the global option. Pass `{ animate: false }` to override. +```js +panzoom.reset() +panzoom.reset({ animate: false }) +``` + #### Signature with return type: ▸ (`resetOptions?`: [PanzoomOptions](#PanzoomOptions)): _[CurrentValues](#CurrentValues)_ @@ -573,11 +510,11 @@ Pass `{ animate: false }` to override. --- -### setOptions +### setOptions() • **setOptions**: _function_ -_Defined in [types.ts:136](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L136)_ +_Defined in [types.ts:146](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L146)_ Change options for the Panzoom instance @@ -593,21 +530,21 @@ Change options for the Panzoom instance --- -### setStyle +### setStyle() • **setStyle**: _`setStyle`_ -_Defined in [types.ts:138](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L138)_ +_Defined in [types.ts:148](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L148)_ A convenience method for setting prefixed styles on the Panzoom element --- -### zoom +### zoom() • **zoom**: _function_ -_Defined in [types.ts:147](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L147)_ +_Defined in [types.ts:157](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L157)_ Zoom the Panzoom element to the given scale @@ -618,75 +555,89 @@ panzoom.zoom(2.2, { animate: true }) #### Signature with return type: -▸ (`scale`: number, `zoomOptions?`: ZoomOptions): _[CurrentValues](#CurrentValues)_ +▸ (`scale`: number, `zoomOptions?`: [ZoomOptions](#ZoomOptions)): _[CurrentValues](#CurrentValues)_ **Parameters:** -| Name | Type | -| -------------- | ----------- | -| `scale` | number | -| `zoomOptions?` | ZoomOptions | +| Name | Type | +| -------------- | --------------------------- | +| `scale` | number | +| `zoomOptions?` | [ZoomOptions](#ZoomOptions) | --- -### zoomIn +### zoomIn() • **zoomIn**: _function_ -_Defined in [types.ts:153](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L153)_ +_Defined in [types.ts:168](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L168)_ -Zoom in using the predetermined increment set in options +Zoom in using the predetermined increment set in options. Animates by default, ignoring the global option. Pass `{ animate: false }` to override. +```js +panzoom.zoomIn() +panzoom.zoomIn({ animate: false }) +``` + #### Signature with return type: -▸ (`zoomOptions?`: ZoomOptions): _[CurrentValues](#CurrentValues)_ +▸ (`zoomOptions?`: [ZoomOptions](#ZoomOptions)): _[CurrentValues](#CurrentValues)_ **Parameters:** -| Name | Type | -| -------------- | ----------- | -| `zoomOptions?` | ZoomOptions | +| Name | Type | +| -------------- | --------------------------- | +| `zoomOptions?` | [ZoomOptions](#ZoomOptions) | --- -### zoomOut +### zoomOut() • **zoomOut**: _function_ -_Defined in [types.ts:159](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L159)_ +_Defined in [types.ts:179](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L179)_ -Zoom out using the predetermined increment set in options +Zoom out using the predetermined increment set in options. Animates by default, ignoring the global option. Pass `{ animate: false }` to override. +```js +panzoom.zoomOut() +panzoom.zoomOut({ animate: false }) +``` + #### Signature with return type: -▸ (`zoomOptions?`: ZoomOptions): _[CurrentValues](#CurrentValues)_ +▸ (`zoomOptions?`: [ZoomOptions](#ZoomOptions)): _[CurrentValues](#CurrentValues)_ **Parameters:** -| Name | Type | -| -------------- | ----------- | -| `zoomOptions?` | ZoomOptions | +| Name | Type | +| -------------- | --------------------------- | +| `zoomOptions?` | [ZoomOptions](#ZoomOptions) | --- -### zoomToPoint +### zoomToPoint() • **zoomToPoint**: _function_ -_Defined in [types.ts:166](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L166)_ +_Defined in [types.ts:190](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L190)_ Zoom the Panzoom element to a focal point using the given pointer/touch/mouse event or constructed point. The clientX/clientY values should be calculated the same way as a pointer event on the Panzoom element. +```js +panzoom.zoomToPoint(1.2, pointerEvent) +``` + #### Signature with return type: -▸ (`scale`: number, `point`: object, `zoomOptions?`: ZoomOptions): _[CurrentValues](#CurrentValues)_ +▸ (`scale`: number, `point`: object, `zoomOptions?`: [ZoomOptions](#ZoomOptions)): _[CurrentValues](#CurrentValues)_ **Parameters:** @@ -699,15 +650,15 @@ the same way as a pointer event on the Panzoom element. | `clientX` | number | | `clientY` | number | -▪`Optional` **zoomOptions**: _ZoomOptions_ +▪`Optional` **zoomOptions**: _[ZoomOptions](#ZoomOptions)_ --- -### zoomWithWheel +### zoomWithWheel() • **zoomWithWheel**: _function_ -_Defined in [types.ts:195](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L195)_ +_Defined in [types.ts:219](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L219)_ Zoom the Panzoom element to a focal point using the given WheelEvent @@ -724,24 +675,24 @@ method or the `zoom` method's focal option. ```js // Bind to mousewheel -elem.parentElement.addEventListener('wheel', panzoom.zoomUsingWheel) +elem.parentElement.addEventListener('wheel', panzoom.zoomWithWheel) // Bind to shift+mousewheel elem.parentElement.addEventListener('wheel', function(event) { if (!event.shiftKey) return - panzoom.zoomUsingWheel(event) + panzoom.zoomWithWheel(event) }) ``` #### Signature with return type: -▸ (`event`: `WheelEvent`, `zoomOptions?`: ZoomOptions): _[CurrentValues](#CurrentValues)_ +▸ (`event`: `WheelEvent`, `zoomOptions?`: [ZoomOptions](#ZoomOptions)): _[CurrentValues](#CurrentValues)_ **Parameters:** -| Name | Type | -| -------------- | ------------ | -| `event` | `WheelEvent` | -| `zoomOptions?` | ZoomOptions | +| Name | Type | +| -------------- | --------------------------- | +| `event` | `WheelEvent` | +| `zoomOptions?` | [ZoomOptions](#ZoomOptions) | --- @@ -751,7 +702,7 @@ elem.parentElement.addEventListener('wheel', function(event) { • **scale**: _number_ -_Defined in [types.ts:108](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L108)_ +_Defined in [types.ts:113](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L113)_ --- @@ -759,7 +710,7 @@ _Defined in [types.ts:108](https://github.com/timmywil/panzoom/blob/213f0df/src/ • **x**: _number_ -_Defined in [types.ts:106](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L106)_ +_Defined in [types.ts:111](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L111)_ --- @@ -767,4 +718,45 @@ _Defined in [types.ts:106](https://github.com/timmywil/panzoom/blob/213f0df/src/ • **y**: _number_ -_Defined in [types.ts:107](https://github.com/timmywil/panzoom/blob/213f0df/src/types.ts#L107)_ +_Defined in [types.ts:112](https://github.com/timmywil/panzoom/blob/b4b8329/src/types.ts#L112)_ + +## Events + +The following events are available as custom events on the panzoom element using the native [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) API. +Add listeners the same way you would any other event. + +```js +elem.addEventListener('panzoomchange', (event) => { + console.log(event.detail) // => { x: 0, y: 0, scale: 1 } +}) +``` + +### Notes about all events + +- The event object passed as an argument to the listener will always have a `detail` property with the current `x`, `y`, and `scale` values. +- Events can be silenced when the `silent` option is set to `true`, either globally or when passed to `pan`, any `zoom` method, or `reset`. +- Avoid putting too much logic in these event handlers as it could effect the performance of panning or zooming. + +### `"panzoomstart"` + +Fired when the user starts a move or pinch zoom gesture on mobile. + +### `"panzoomchange"` + +Fired whenever there is a pan, zoom, or reset. Note that direct calls to `options.setTransform` do not fire this event. + +### `"panzoomzoom"` + +Fired whenever the zoom is changed by any Panzoom `zoom` method, directly or internally. + +### `"panzoompan"` + +Fired whenever the zoom is changed by the `pan` method, directly or internally. + +### `"panzoomend"` + +Fired when the user finishes a move or finishes a pinch zoom gesture on mobile. + +### `"panzoomreset"` + +Fired whenever reset is called. diff --git a/demo/examples/ContainOutside.tsx b/demo/examples/ContainOutside.tsx index 552b2951..cc4955fb 100644 --- a/demo/examples/ContainOutside.tsx +++ b/demo/examples/ContainOutside.tsx @@ -11,7 +11,7 @@ export default function ContainOutside() { Panzoom(elem.current, { contain: 'outside', startScale: 1.5 }) }, []) return ( - +
elements are SVG, we want to treat those like other elements - this.isSVG = rsvg.test(elem.namespaceURI) && elem.nodeName.toLowerCase() !== 'svg' - - this.panning = false - - // Save the original transform value - // Save the prefixed transform style key - // Set the starting transform - this._buildTransform() - - // Build the appropriately-prefixed transform style property name - // De-camelcase - this._transform = $.cssProps.transform - ? $.cssProps.transform.replace(rupper, '-$1').toLowerCase() - : 'transform' - - // Build the transition value - this._buildTransition() - - // Build containment dimensions - this.resetDimensions() - - // Add zoom and reset buttons to `this` - var $empty = $() - var self = this - $.each(['$zoomIn', '$zoomOut', '$zoomRange', '$reset'], function(i, name) { - self[name] = options[name] || $empty - }) - - this.enable() - - this.scale = this.getMatrix()[0] - this._checkPanWhenZoomed() - - // Save the instance - $.data(elem, datakey, this) - } - - // Attach regex for possible use (immutable) - Panzoom.rmatrix = rmatrix - - Panzoom.defaults = { - // Should always be non-empty - // Used to bind jQuery events without collisions - // A guid is not added here as different instantiations/versions of panzoom - // on the same element is not supported, so don't do it. - eventNamespace: '.panzoom', - - // Whether or not to transition the scale - transition: true, - - // Default cursor style for the element - cursor: 'move', - - // There may be some use cases for zooming without panning or vice versa - disablePan: false, - disableZoom: false, - - // Pan only on the X or Y axes - disableXAxis: false, - disableYAxis: false, - - // Set whether you'd like to pan on left (1), middle (2), or right click (3) - which: 1, - - // The increment at which to zoom - // Should be a number greater than 0 - increment: 0.3, - - // When no scale is passed, this option tells - // the `zoom` method to increment - // the scale *linearly* based on the increment option. - // This often ends up looking like very little happened at larger zoom levels. - // The default is to multiply/divide the scale based on the increment. - linearZoom: false, - - // Pan only when the scale is greater than minScale - panOnlyWhenZoomed: false, - - // min and max zoom scales - minScale: 0.3, - maxScale: 6, - - // The default step for the range input - // Precendence: default < HTML attribute < option setting - rangeStep: 0.05, - - // Animation duration (ms) - duration: 200, - // CSS easing used for scale transition - easing: 'ease-in-out', - - // Indicate that the element should be contained within it's parent when panning - // Note: this does not affect zooming outside of the parent - // Set this value to 'invert' to only allow panning outside of the parent element (basically the opposite of the normal use of contain) - // 'invert' is useful for a large panzoom element where you don't want to show anything behind it - contain: false - } - - Panzoom.prototype = { - constructor: Panzoom, - - /** - * @returns {Panzoom} Returns the instance - */ - instance: function() { - return this - }, - - /** - * Enable or re-enable the panzoom instance - */ - enable: function() { - // Unbind first - this._initStyle() - this._bind() - this.disabled = false - }, - - /** - * Disable panzoom - */ - disable: function() { - this.disabled = true - this._resetStyle() - this._unbind() - }, - - /** - * @returns {Boolean} Returns whether the current panzoom instance is disabled - */ - isDisabled: function() { - return this.disabled - }, - - /** - * Destroy the panzoom instance - */ - destroy: function() { - this.disable() - $.removeData(this.elem, datakey) - }, - - /** - * Builds the restricing dimensions from the containment element - * Also used with focal points - * Call this method whenever the dimensions of the element or parent are changed - */ - resetDimensions: function() { - // Reset container properties - this.container = this.parent.getBoundingClientRect() - - // Set element properties - var elem = this.elem - // getBoundingClientRect() works with SVG, offsetWidth does not - var dims = elem.getBoundingClientRect() - var absScale = Math.abs(this.scale) - this.dimensions = { - width: dims.width, - height: dims.height, - left: $.css(elem, 'left', true) || 0, - top: $.css(elem, 'top', true) || 0, - // Borders and margins are scaled - border: { - top: $.css(elem, 'borderTopWidth', true) * absScale || 0, - bottom: $.css(elem, 'borderBottomWidth', true) * absScale || 0, - left: $.css(elem, 'borderLeftWidth', true) * absScale || 0, - right: $.css(elem, 'borderRightWidth', true) * absScale || 0 - }, - margin: { - top: $.css(elem, 'marginTop', true) * absScale || 0, - left: $.css(elem, 'marginLeft', true) * absScale || 0 - } - } - }, - - /** - * Return the element to it's original transform matrix - * @param {Boolean} [options] If a boolean is passed, animate the reset (default: true). If an options object is passed, simply pass that along to setMatrix. - * @param {Boolean} [options.silent] Silence the reset event - */ - reset: function(options) { - options = createResetOptions(options) - // Reset the transform to its original value - var matrix = this.setMatrix(this._origTransform, options) - if (!options.silent) { - this._trigger('reset', matrix) - } - }, - - /** - * Only resets zoom level - * @param {Boolean|Object} [options] Whether to animate the reset (default: true) or an object of options to pass to zoom() - */ - resetZoom: function(options) { - options = createResetOptions(options) - var origMatrix = this.getMatrix(this._origTransform) - options.dValue = origMatrix[3] - this.zoom(origMatrix[0], options) - }, - - /** - * Only reset panning - * @param {Boolean|Object} [options] Whether to animate the reset (default: true) or an object of options to pass to pan() - */ - resetPan: function(options) { - var origMatrix = this.getMatrix(this._origTransform) - this.pan(origMatrix[4], origMatrix[5], createResetOptions(options)) - }, - - /** - * Sets a transform on the $set - * For SVG, the style attribute takes precedence - * and allows us to animate - * @param {String} transform - */ - setTransform: function(transform) { - var $set = this.$set - var i = $set.length - while (i--) { - $.style($set[i], 'transform', transform) - - // Support IE9-11, Edge 13-14+ - // Set attribute alongside style attribute - // since IE and Edge do not respect style settings on SVG - // See https://css-tricks.com/transforms-on-svg-elements/ - if (this.isSVG) { - $set[i].setAttribute('transform', transform) - } - } - }, - - /** - * Retrieving the transform is different for SVG - * (unless a style transform is already present) - * Uses the $set collection for retrieving the transform - * @param {String} [transform] Pass in an transform value (like 'scale(1.1)') - * to have it formatted into matrix format for use by Panzoom - * @returns {String} Returns the current transform value of the element - */ - getTransform: function(transform) { - var $set = this.$set - var transformElem = $set[0] - if (transform) { - this.setTransform(transform) - } else { - // IE and Edge still set the transform style properly - // They just don't render it on SVG - // So we get a correct value here - transform = $.style(transformElem, 'transform') - - if (this.isSVG && (!transform || transform === 'none')) { - transform = $.attr(transformElem, 'transform') || 'none' - } - } - - // Convert any transforms set by the user to matrix format - // by setting to computed - if (transform !== 'none' && !rmatrix.test(transform)) { - // Get computed and set for next time - this.setTransform((transform = $.css(transformElem, 'transform'))) - } - - return transform || 'none' - }, - - /** - * Retrieve the current transform matrix for $elem (or turn a transform into it's array values) - * @param {String} [transform] matrix-formatted transform value - * @returns {Array} Returns the current transform matrix split up into it's parts, or a default matrix - */ - getMatrix: function(transform) { - var matrix = rmatrix.exec(transform || this.getTransform()) - if (matrix) { - matrix.shift() - } - return matrix || [1, 0, 0, 1, 0, 0] - }, - - /** - * Get the current scale. - * @param {String} [transform] matrix-formatted transform value - * @returns {Number} Current scale relative to the initial scale (height / width = 1) - */ - getScale: function(matrix) { - return Math.sqrt(Math.pow(matrix[0], 2) + Math.pow(matrix[1], 2)) - }, - - /** - * Given a matrix object, quickly set the current matrix of the element - * @param {Array|String} matrix - * @param {Object} [options] - * @param {Boolean|String} [options.animate] Whether to animate the transform change, or 'skip' indicating that it is unnecessary to set - * @param {Boolean} [options.contain] Override the global contain option - * @param {Boolean} [options.range] If true, $zoomRange's value will be updated. - * @param {Boolean} [options.silent] If true, the change event will not be triggered - * @returns {Array} Returns the newly-set matrix - */ - setMatrix: function(matrix, options) { - if (this.disabled) { - return - } - if (!options) { - options = {} - } - // Convert to array - if (typeof matrix === 'string') { - matrix = this.getMatrix(matrix) - } - var scale = this.getScale(matrix) - var contain = typeof options.contain !== 'undefined' ? options.contain : this.options.contain - - // Apply containment - if (contain) { - var dims = options.dims - if (!dims) { - this.resetDimensions() - dims = this.dimensions - } - var spaceWLeft, spaceWRight, scaleDiff - var container = this.container - var width = dims.width - var height = dims.height - var conWidth = container.width - var conHeight = container.height - var zoomAspectW = conWidth / width - var zoomAspectH = conHeight / height - - // If the element is not naturally centered, - // assume full space right - if ( - this.$parent.css('textAlign') !== 'center' || - $.css(this.elem, 'display') !== 'inline' - ) { - // offsetWidth gets us the width without the transform - scaleDiff = (width - this.elem.offsetWidth) / 2 - spaceWLeft = scaleDiff - dims.border.left - spaceWRight = width - conWidth - scaleDiff + dims.border.right - } else { - spaceWLeft = spaceWRight = (width - conWidth) / 2 - } - var spaceHTop = (height - conHeight) / 2 + dims.border.top - var spaceHBottom = (height - conHeight) / 2 - dims.border.top - dims.border.bottom - - if (contain === 'invert' || (contain === 'automatic' && zoomAspectW < 1.01)) { - matrix[4] = Math.max(Math.min(matrix[4], spaceWLeft - dims.border.left), -spaceWRight) - } else { - matrix[4] = Math.min(Math.max(matrix[4], spaceWLeft), -spaceWRight) - } - - if (contain === 'invert' || (contain === 'automatic' && zoomAspectH < 1.01)) { - matrix[5] = Math.max(Math.min(matrix[5], spaceHTop - dims.border.top), -spaceHBottom) - } else { - matrix[5] = Math.min(Math.max(matrix[5], spaceHTop), -spaceHBottom) - } - } - - // Animate - if (options.animate !== 'skip') { - // Set transition - this.transition(!options.animate) - } - - // Update range element - if (options.range) { - this.$zoomRange.val(scale) - } - - // Set the matrix on this.$set - if (this.options.disableXAxis || this.options.disableYAxis) { - var originalMatrix = this.getMatrix() - if (this.options.disableXAxis) { - matrix[4] = originalMatrix[4] - } - if (this.options.disableYAxis) { - matrix[5] = originalMatrix[5] - } - } - this.setTransform('matrix(' + matrix.join(',') + ')') - - this.scale = scale - - // Disable/enable panning if zooming is at minimum and panOnlyWhenZoomed is true - this._checkPanWhenZoomed(scale) - - if (!options.silent) { - this._trigger('change', matrix) - } - - return matrix - }, - - /** - * @returns {Boolean} Returns whether the panzoom element is currently being dragged - */ - isPanning: function() { - return this.panning - }, - - /** - * Apply the current transition to the element, if allowed - * @param {Boolean} [off] Indicates that the transition should be turned off - */ - transition: function(off) { - if (!this._transition) { - return - } - var transition = off || !this.options.transition ? 'none' : this._transition - var $set = this.$set - var i = $set.length - while (i--) { - // Avoid reflows when zooming - if ($.style($set[i], 'transition') !== transition) { - $.style($set[i], 'transition', transition) - } - } - }, - - /** - * Pan the element to the specified translation X and Y - * Note: this is not the same as setting jQuery#offset() or jQuery#position() - * @param {Number} x - * @param {Number} y - * @param {Object} [options] These options are passed along to setMatrix - * @param {Array} [options.matrix] The matrix being manipulated (if already known so it doesn't have to be retrieved again) - * @param {Boolean} [options.silent] Silence the pan event. Note that this will also silence the setMatrix change event. - * @param {Boolean} [options.relative] Make the x and y values relative to the existing matrix - */ - pan: function(x, y, options) { - if (this.options.disablePan) { - return - } - if (!options) { - options = {} - } - var matrix = options.matrix - if (!matrix) { - matrix = this.getMatrix() - } - // Cast existing matrix values to numbers - if (options.relative) { - x += +matrix[4] - y += +matrix[5] - } - matrix[4] = x - matrix[5] = y - this.setMatrix(matrix, options) - if (!options.silent) { - this._trigger('pan', matrix[4], matrix[5]) - } - }, - - /** - * Zoom in/out the element using the scale properties of a transform matrix - * @param {Number|Boolean} [scale] The scale to which to zoom or a boolean indicating to transition a zoom out - * @param {Object} [opts] All global options can be overwritten by this options object. For example, override the default increment. - * @param {Boolean} [opts.noSetRange] Specify that the method should not set the $zoomRange value (as is the case when $zoomRange is calling zoom on change) - * @param {jQuery.Event|Object} [opts.focal] A focal point on the panzoom element on which to zoom. - * If an object, set the clientX and clientY properties to the position relative to the parent - * @param {Boolean} [opts.animate] Whether to animate the zoom (defaults to true if scale is not a number, false otherwise) - * @param {Boolean} [opts.silent] Silence the zoom event - * @param {Array} [opts.matrix] Optionally pass the current matrix so it doesn't need to be retrieved - * @param {Number} [opts.dValue] Think of a transform matrix as four values a, b, c, d - * where a/d are the horizontal/vertical scale values and b/c are the skew values - * (5 and 6 of matrix array are the tx/ty transform values). - * Normally, the scale is set to both the a and d values of the matrix. - * This option allows you to specify a different d value for the zoom. - * For instance, to flip vertically, you could set -1 as the dValue. - */ - zoom: function(scale, opts) { - // Shuffle arguments - if (typeof scale === 'object') { - opts = scale - scale = null - } else if (!opts) { - opts = {} - } - var options = $.extend({}, this.options, opts) - // Check if disabled - if (options.disableZoom) { - return - } - var animate = false - var matrix = options.matrix || this.getMatrix() - var surfaceM = new Matrix(matrix) - var startScale = this.getScale(matrix) - - // Calculate zoom based on increment - if (typeof scale !== 'number') { - if (options.linearZoom) { - scale = 1 + (options.increment * (scale ? -1 : 1)) / startScale - } else { - scale = scale ? 1 / (1 + options.increment) : 1 + options.increment - } - animate = true - } else { - scale = 1 / startScale - } - - // Constrain scale - scale = Math.max( - Math.min(scale, options.maxScale / startScale), - options.minScale / startScale - ) - var m = surfaceM.x( - new Matrix( - scale, - 0, - 0, - 0, - typeof options.dValue === 'number' ? options.dValue / startScale : scale, - 0 - ) - ) - - // Calculate focal point based on scale - var focal = options.focal - if (focal && !options.disablePan) { - // Adapted from code by Florian Günther - // https://github.com/florianguenther/zui53 - this.resetDimensions() - var dims = (options.dims = this.dimensions) - var clientX = focal.clientX - var clientY = focal.clientY - - // Adjust the focal point for transform-origin 50% 50% - // SVG elements have a transform origin of 0 0 - if (!this.isSVG) { - clientX -= dims.width / startScale / 2 - clientY -= dims.height / startScale / 2 - } - - var clientV = new Vector(clientX, clientY, 1) - // Supply an offset manually if necessary - var o = this.parentOffset || this.$parent.offset() - var offsetM = new Matrix( - 1, - 0, - o.left - this.$doc.scrollLeft(), - 0, - 1, - o.top - this.$doc.scrollTop() - ) - var surfaceV = surfaceM.inverse().x(offsetM.inverse().x(clientV)) - surfaceM = surfaceM.x(new Matrix([scale, 0, 0, scale, 0, 0])) - clientV = offsetM.x(surfaceM.x(surfaceV)) - matrix[4] = +matrix[4] + (clientX - clientV.e(0)) - matrix[5] = +matrix[5] + (clientY - clientV.e(1)) - } - - // Set the scale - matrix[0] = m.e(0) - matrix[1] = m.e(3) - matrix[2] = m.e(1) - matrix[3] = m.e(4) - - // Calling zoom may still pan the element - this.setMatrix(matrix, { - animate: typeof options.animate !== 'undefined' ? options.animate : animate, - // Set the zoomRange value - range: !options.noSetRange - }) - - // Trigger zoom event - if (!options.silent) { - this._trigger('zoom', scale, options) - } - }, - - /** - * Get/set option on an existing instance - * @returns {Array|undefined} If getting, returns an array of all values - * on each instance for a given key. If setting, continue chaining by returning undefined. - */ - option: function(key, value) { - var options - if (!key) { - // Avoids returning direct reference - return $.extend({}, this.options) - } - - if (typeof key === 'string') { - if (arguments.length === 1) { - return this.options[key] !== undefined ? this.options[key] : null - } - options = {} - options[key] = value - } else { - options = key - } - - this._setOptions(options) - }, - - /** - * Internally sets options - * @param {Object} options - An object literal of options to set - * @private - */ - _setOptions: function(options) { - $.each( - options, - $.proxy(function(key, value) { - switch (key) { - case 'disablePan': - this._resetStyle() - /* falls through */ - case '$zoomIn': - case '$zoomOut': - case '$zoomRange': - case '$reset': - case 'disableZoom': - case 'onStart': - case 'onChange': - case 'onZoom': - case 'onPan': - case 'onEnd': - case 'onReset': - case 'eventNamespace': - this._unbind() - } - this.options[key] = value - switch (key) { - case 'disablePan': - this._initStyle() - /* falls through */ - case '$zoomIn': - case '$zoomOut': - case '$zoomRange': - case '$reset': - // Set these on the instance - this[key] = value - /* falls through */ - case 'disableZoom': - case 'onStart': - case 'onChange': - case 'onZoom': - case 'onPan': - case 'onEnd': - case 'onReset': - case 'eventNamespace': - this._bind() - break - case 'cursor': - $.style(this.elem, 'cursor', value) - break - case 'minScale': - this.$zoomRange.attr('min', value) - break - case 'maxScale': - this.$zoomRange.attr('max', value) - break - case 'rangeStep': - this.$zoomRange.attr('step', value) - break - case 'startTransform': - this._buildTransform() - break - case 'duration': - case 'easing': - this._buildTransition() - /* falls through */ - case 'transition': - this.transition() - break - case 'panOnlyWhenZoomed': - this._checkPanWhenZoomed() - break - case '$set': - if (value instanceof $ && value.length) { - this.$set = value - // Reset styles - this._initStyle() - this._buildTransform() - } - } - }, this) - ) - }, - - /** - * Disable/enable panning depending on whether the current scale - * matches the minimum - * @param {Number} [scale] - * @private - */ - _checkPanWhenZoomed: function(scale) { - var options = this.options - if (options.panOnlyWhenZoomed) { - if (!scale) { - scale = this.getMatrix()[0] - } - var toDisable = scale <= options.minScale - if (options.disablePan !== toDisable) { - this.option('disablePan', toDisable) - } - } - }, - - /** - * Initialize base styles for the element and its parent - * @private - */ - _initStyle: function() { - var styles = { - // Set the same default whether SVG or HTML - // transform-origin cannot be changed to 50% 50% in IE9-11 or Edge 13-14+ - 'transform-origin': this.isSVG ? '0 0' : '50% 50%' - } - // Set elem styles - if (!this.options.disablePan) { - styles.cursor = this.options.cursor - } - this.$set.css(styles) - - // Set parent to relative if set to static - var $parent = this.$parent - // No need to add styles to the body - if ($parent.length && !$.nodeName(this.parent, 'body')) { - styles = { - overflow: 'hidden' - } - if ($parent.css('position') === 'static') { - styles.position = 'relative' - } - $parent.css(styles) - } - }, - - /** - * Undo any styles attached in this plugin - * @private - */ - _resetStyle: function() { - this.$elem.css({ - cursor: '', - transition: '' - }) - this.$parent.css({ - overflow: '', - position: '' - }) - }, - - /** - * Binds all necessary events - * @private - */ - _bind: function() { - var self = this - var options = this.options - var ns = options.eventNamespace - var str_down = 'mousedown' + ns + ' pointerdown' + ns + ' MSPointerDown' + ns - var str_start = 'touchstart' + ns + ' ' + str_down - var str_click = 'touchend' + ns + ' click' + ns + ' pointerup' + ns + ' MSPointerUp' + ns - var events = {} - var $reset = this.$reset - var $zoomRange = this.$zoomRange - - // Bind panzoom events from options - $.each(['Start', 'Change', 'Zoom', 'Pan', 'End', 'Reset'], function() { - var m = options['on' + this] - if ($.isFunction(m)) { - events['panzoom' + this.toLowerCase() + ns] = m - } - }) - - // Bind $elem drag and click/touchdown events - // Bind touchstart if either panning or zooming is enabled - if (!options.disablePan || !options.disableZoom) { - events[str_start] = function(e) { - var touches - if ( - e.type === 'touchstart' - ? // Touch - (touches = e.touches || e.originalEvent.touches) && - ((touches.length === 1 && !options.disablePan) || touches.length === 2) - : // Mouse/Pointer: Ignore unexpected click types - // Support: IE10 only - // IE10 does not support e.button for MSPointerDown, but does have e.which - !options.disablePan && (e.which || e.originalEvent.which) === options.which - ) { - e.preventDefault() - e.stopPropagation() - self._startMove(e, touches) - } - } - // Prevent the contextmenu event - // if we're binding to right-click - if (options.which === 3) { - events.contextmenu = false - } - } - this.$elem.on(events) - - // Bind reset - if ($reset.length) { - $reset.on(str_click, function(e) { - e.preventDefault() - self.reset() - }) - } - - // Set default attributes for the range input - if ($zoomRange.length) { - $zoomRange - .attr({ - // Only set the range step if explicit or - // set the default if there is no attribute present - step: - (options.rangeStep === Panzoom.defaults.rangeStep && $zoomRange.attr('step')) || - options.rangeStep, - min: options.minScale, - max: options.maxScale - }) - .prop({ - value: this.getMatrix()[0] - }) - } - - // No bindings if zooming is disabled - if (options.disableZoom) { - return - } - - var $zoomIn = this.$zoomIn - var $zoomOut = this.$zoomOut - - // Bind zoom in/out - // Don't bind one without the other - if ($zoomIn.length && $zoomOut.length) { - // preventDefault cancels future mouse events on touch events - $zoomIn.on(str_click, function(e) { - e.preventDefault() - self.zoom() - }) - $zoomOut.on(str_click, function(e) { - e.preventDefault() - self.zoom(true) - }) - } - - if ($zoomRange.length) { - events = {} - // Cannot prevent default action here - events[str_down] = function() { - self.transition(true) - } - // Zoom on input events if available and change events - // See https://github.com/timmywil/jquery.panzoom/issues/90 - events[(supportsInputEvent ? 'input' : 'change') + ns] = function() { - self.zoom(+this.value, { noSetRange: true }) - } - $zoomRange.on(events) - } - }, - - /** - * Unbind all events - * @private - */ - _unbind: function() { - this.$elem - .add(this.$zoomIn) - .add(this.$zoomOut) - .add(this.$reset) - .off(this.options.eventNamespace) - }, - - /** - * Builds the original transform value - * @private - */ - _buildTransform: function() { - // Save the original transform - // Retrieving this also adds the correct prefixed style name - // to jQuery's internal $.cssProps - return (this._origTransform = this.getTransform(this.options.startTransform)) - }, - - /** - * Set transition property for later use when zooming - * @private - */ - _buildTransition: function() { - if (this._transform) { - var options = this.options - this._transition = this._transform + ' ' + options.duration + 'ms ' + options.easing - } - }, - - /** - * Calculates the distance between two touch points - * Remember pythagorean? - * @param {Array} touches - * @returns {Number} Returns the distance - * @private - */ - _getDistance: function(touches) { - var touch1 = touches[0] - var touch2 = touches[1] - return Math.sqrt( - Math.pow(Math.abs(touch2.clientX - touch1.clientX), 2) + - Math.pow(Math.abs(touch2.clientY - touch1.clientY), 2) - ) - }, - - /** - * Constructs an approximated point in the middle of two touch points - * @returns {Object} Returns an object containing pageX and pageY - * @private - */ - _getMiddle: function(touches) { - var touch1 = touches[0] - var touch2 = touches[1] - return { - clientX: (touch2.clientX - touch1.clientX) / 2 + touch1.clientX, - clientY: (touch2.clientY - touch1.clientY) / 2 + touch1.clientY - } - }, - - /** - * Trigger a panzoom event on our element - * The event is passed the Panzoom instance - * @param {String|jQuery.Event} event - * @param {Mixed} arg1[, arg2, arg3, ...] Arguments to append to the trigger - * @private - */ - _trigger: function(event) { - if (typeof event === 'string') { - event = 'panzoom' + event - } - this.$elem.triggerHandler(event, [this].concat(slice.call(arguments, 1))) - }, - - /** - * Starts the pan - * This is bound to mouse/touchmove on the element - * @param {jQuery.Event} event An event with pageX, pageY, and possibly the touches list - * @param {TouchList} [touches] The touches list if present - * @private - */ - _startMove: function(event, touches) { - if (this.panning) { - return - } - var moveEvent, endEvent, startDistance, startScale, startMiddle, startPageX, startPageY, touch - var self = this - var options = this.options - var ns = options.eventNamespace - var matrix = this.getMatrix() - var original = matrix.slice(0) - var origPageX = +original[4] - var origPageY = +original[5] - var panOptions = { matrix: matrix, animate: 'skip' } - var type = event.type - - // Use proper events - if (type === 'pointerdown') { - moveEvent = 'pointermove' - endEvent = 'pointerup' - } else if (type === 'touchstart') { - moveEvent = 'touchmove' - endEvent = 'touchend' - } else if (type === 'MSPointerDown') { - moveEvent = 'MSPointerMove' - endEvent = 'MSPointerUp' - } else { - moveEvent = 'mousemove' - endEvent = 'mouseup' - } - - // Add namespace - moveEvent += ns - endEvent += ns - - // Remove any transitions happening - this.transition(true) - - // Indicate that we are currently panning - this.panning = true - - // Trigger start event - this._trigger('start', event, touches) - - var setStart = function(event, touches) { - if (touches) { - if (touches.length === 2) { - if (startDistance != null) { - return - } - startDistance = self._getDistance(touches) - startScale = self.getScale(matrix) - startMiddle = self._getMiddle(touches) - return - } - if (startPageX != null) { - return - } - if ((touch = touches[0])) { - startPageX = touch.pageX - startPageY = touch.pageY - } - } - if (startPageX != null) { - return - } - startPageX = event.pageX - startPageY = event.pageY - } - - setStart(event, touches) - - var move = function(e) { - var coords - e.preventDefault() - touches = e.touches || e.originalEvent.touches - setStart(e, touches) - - if (touches) { - if (touches.length === 2) { - // Calculate move on middle point - var middle = self._getMiddle(touches) - var diff = self._getDistance(touches) - startDistance - - // Set zoom - self.zoom(diff * (options.increment / 100) + startScale, { - focal: middle, - matrix: matrix, - animate: 'skip' - }) - - // Set pan - self.pan( - +matrix[4] + middle.clientX - startMiddle.clientX, - +matrix[5] + middle.clientY - startMiddle.clientY, - panOptions - ) - startMiddle = middle - return - } - coords = touches[0] || { pageX: 0, pageY: 0 } - } - - if (!coords) { - coords = e - } - - self.pan( - origPageX + coords.pageX - startPageX, - origPageY + coords.pageY - startPageY, - panOptions - ) - } - - // Bind the handlers - $(document) - .off(ns) - .on(moveEvent, move) - .on(endEvent, function(e) { - e.preventDefault() - // Unbind all document events - $(this).off(ns) - self.panning = false - // Trigger our end event - // Simply set the type to "panzoomend" to pass through all end properties - // jQuery's `not` is used here to compare Array equality - e.type = 'panzoomend' - self._trigger(e, matrix, !matrixEquals(matrix, original)) - }) - } - } - - // Add Panzoom as a static property - $.Panzoom = Panzoom - - /** - * Extend jQuery - * @param {Object|String} options - The name of a method to call on the prototype - * or an object literal of options - * @returns {jQuery|Mixed} jQuery instance for regular chaining or the return value(s) of a panzoom method call - */ - $.fn.panzoom = function(options) { - var instance, args, m, ret - - // Call methods widget-style - if (typeof options === 'string') { - ret = [] - args = slice.call(arguments, 1) - this.each(function() { - instance = $.data(this, datakey) - - if (!instance) { - ret.push(undefined) - - // Ignore methods beginning with `_` - } else if ( - options.charAt(0) !== '_' && - typeof (m = instance[options]) === 'function' && - // If nothing is returned, do not add to return values - (m = m.apply(instance, args)) !== undefined - ) { - ret.push(m) - } - }) - - // Return an array of values for the jQuery instances - // Or the value itself if there is only one - // Or keep chaining - return ret.length ? (ret.length === 1 ? ret[0] : ret) : this - } - - return this.each(function() { - new Panzoom(this, options) - }) - } - - return Panzoom -}) diff --git a/package.json b/package.json index 28f1d753..67354826 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "@semantic-release/git": "^7.0.16", "@types/mocha": "^5.2.7", "@types/prismjs": "^1.16.0", - "@types/react": "^16.8.25", "@types/react-dom": "^16.8.5", "commitizen": "^4.0.3", "commitlint": "^8.1.0", @@ -61,7 +60,7 @@ "css-loader": "^3.2.0", "gzip-size-cli": "^3.0.0", "html-webpack-plugin": "^3.2.0", - "husky": "^3.0.2", + "husky": "^3.0.3", "karma": "^4.2.0", "karma-chrome-launcher": "^3.0.0", "karma-mocha": "^1.3.0", @@ -71,8 +70,8 @@ "prettier": "^1.18.2", "prismjs": "^1.17.1", "puppeteer": "^1.19.0", - "react": "^16.8.6", - "react-dom": "^16.8.6", + "react": "^16.9.0", + "react-dom": "^16.9.0", "rollup": "^1.19.4", "rollup-plugin-typescript2": "^0.22.1", "semantic-release": "^15.13.19", diff --git a/src/css.ts b/src/css.ts index f65d9782..fe93709a 100644 --- a/src/css.ts +++ b/src/css.ts @@ -1,4 +1,4 @@ -import { PanzoomOptions } from './types' +import { CurrentValues, PanzoomOptions } from './types' /** * Proper prefixing for cross-browser compatibility @@ -88,7 +88,7 @@ export function setTransition(elem: HTMLElement | SVGElement, options: PanzoomOp */ export function setTransform( elem: HTMLElement | SVGElement, - { x, y, scale }: { x: number; y: number; scale: number }, + { x, y, scale }: CurrentValues, options: PanzoomOptions = {} ) { if (typeof options.animate === 'boolean') { diff --git a/src/events.ts b/src/events.ts new file mode 100644 index 00000000..1640044d --- /dev/null +++ b/src/events.ts @@ -0,0 +1,33 @@ +let events: { down: string; move: string; up: string } +if (typeof (window as any).PointerEvent === 'function') { + events = { + down: 'pointerdown', + move: 'pointermove', + up: 'pointerup pointerleave pointercancel' + } +} else if (typeof (window as any).TouchEvent === 'function') { + events = { + down: 'touchstart', + move: 'touchmove', + up: 'touchend touchcancel' + } +} else { + events = { + down: 'mousedown', + move: 'mousemove', + up: 'mouseup mouseleave' + } +} + +export function onPointer( + event: 'down' | 'move' | 'up', + elem: HTMLElement | SVGElement | Document, + handler: (event: PointerEvent) => void, + eventOpts?: any +) { + events[event].split(' ').forEach((name) => { + ;(elem as HTMLElement).addEventListener< + 'pointerdown' | 'pointermove' | 'pointerup' | 'pointerleave' | 'pointercancel' + >(name as any, handler, eventOpts) + }) +} diff --git a/src/panzoom.ts b/src/panzoom.ts index 70312e9f..cc50bfe3 100644 --- a/src/panzoom.ts +++ b/src/panzoom.ts @@ -8,9 +8,10 @@ * */ import { getDimensions, setStyle, setTransform } from './css' +import { onPointer } from './events' import isAttached from './isAttached' import isSVGElement from './isSVGElement' -import { addEvent, getDistance, getMiddle, removeEvent } from './pointers' +import { addPointer, getDistance, getMiddle, removePointer } from './pointers' import './polyfills' import shallowClone from './shallowClone' import { PanOptions, PanzoomObject, PanzoomOptions, ZoomOptions } from './types' @@ -27,6 +28,7 @@ const defaultOptions: PanzoomOptions = { easing: 'ease-in-out', maxScale: 4, minScale: 0.125, + panOnlyWhenZoomed: false, relative: false, setTransform, startX: 0, @@ -52,8 +54,6 @@ function Panzoom(elem: HTMLElement | SVGElement, options?: PanzoomOptions): Panz } const isSVG = isSVGElement(elem) - // SVG has pointer events, but TypeScript doesn't know that - const htmlElem = elem as HTMLElement function setOptions(opts: PanzoomOptions = {}) { for (const key in opts) { @@ -100,6 +100,22 @@ function Panzoom(elem: HTMLElement | SVGElement, options?: PanzoomOptions): Panz pan(options.startX, options.startY, { animate: false }) }) + function trigger(eventName: string, detail: any, opts: PanzoomOptions) { + if (opts.silent) { + return + } + const event = new CustomEvent(eventName, { detail }) + elem.dispatchEvent(event) + } + + function setTransformWithEvent(eventName: string, opts: PanzoomOptions) { + const value = { x, y, scale } + opts.setTransform(elem, value, opts) + trigger(eventName, value, opts) + trigger('panzoomchange', value, opts) + return value + } + function constrainXY(toX: number | string, toY: number | string, panOptions?: PanOptions) { const opts = { ...options, ...panOptions } const result = { x, y, opts } @@ -186,9 +202,7 @@ function Panzoom(elem: HTMLElement | SVGElement, options?: PanzoomOptions): Panz x = result.x y = result.y - const values = { x, y, scale } - opts.setTransform(elem, values, opts) - return values + return setTransformWithEvent('panzoompan', opts) } function zoom(toScale: number, zoomOptions?: ZoomOptions) { @@ -212,9 +226,7 @@ function Panzoom(elem: HTMLElement | SVGElement, options?: PanzoomOptions): Panz } scale = toScale - const values = { x, y, scale } - opts.setTransform(elem, values, opts) - return values + return setTransformWithEvent('panzoomzoom', opts) } function zoomInOut(isIn: boolean, zoomOptions?: ZoomOptions) { @@ -310,55 +322,51 @@ function Panzoom(elem: HTMLElement | SVGElement, options?: PanzoomOptions): Panz x = panResult.x y = panResult.y scale = constrainScale(opts.startScale, opts).scale - const values = { x, y, scale } - opts.setTransform(elem, values, opts) - return values + return setTransformWithEvent('panzoomreset', opts) } let origX: number let origY: number - let startX: number - let startY: number + let startClientX: number + let startClientY: number let startScale: number let startDistance: number const pointers: PointerEvent[] = [] function handleDown(event: PointerEvent) { - addEvent(pointers, event) - if (event.pointerId) { - elem.setPointerCapture(event.pointerId) - } // Don't handle this event if the target is a clickable if (event.target && (event.target as Element).classList.contains(options.clickableClass)) { return } + addPointer(pointers, event) isPanning = true event.preventDefault() event.stopPropagation() origX = x origY = y + trigger('panzoomstart', { x, y, scale }, options) + // This works whether there are multiple // pointers or not const point = getMiddle(pointers) - startX = point.clientX - startY = point.clientY + startClientX = point.clientX + startClientY = point.clientY startScale = scale startDistance = getDistance(pointers) } function move(event: PointerEvent) { - // console.log(elem, event.type, event.pointerId) if ( !isPanning || origX === undefined || origY === undefined || - startX === undefined || - startY === undefined + startClientX === undefined || + startClientY === undefined ) { return } - addEvent(pointers, event) + addPointer(pointers, event) const current = getMiddle(pointers) if (pointers.length > 1) { // Use the distance between the first 2 pointers @@ -368,28 +376,34 @@ function Panzoom(elem: HTMLElement | SVGElement, options?: PanzoomOptions): Panz zoomToPoint(toScale, current) } - pan(origX + (current.clientX - startX) / scale, origY + (current.clientY - startY) / scale, { - animate: false - }) + pan( + origX + (current.clientX - startClientX) / scale, + origY + (current.clientY - startClientY) / scale, + { + animate: false + } + ) } function handleUp(event: PointerEvent) { + if (!isPanning) { + return + } + // Only call panzoomend once + if (pointers.length === 1) { + trigger('panzoomend', { x, y, scale }, options) + } // Note: don't remove all pointers // Can restart without having to reinitiate all of them - removeEvent(pointers, event) - if (event.pointerId) { - elem.releasePointerCapture(event.pointerId) - } + removePointer(pointers, event) isPanning = false - origX = origY = startX = startY = undefined + origX = origY = startClientX = startClientY = undefined } if (!options.disablePan) { - htmlElem.addEventListener('pointerdown', handleDown) - htmlElem.addEventListener('pointermove', move, { passive: true }) - htmlElem.addEventListener('pointerup', handleUp, { passive: true }) - htmlElem.addEventListener('pointerleave', handleUp, { passive: true }) - htmlElem.addEventListener('pointercancel', handleUp, { passive: true }) + onPointer('down', elem, handleDown) + onPointer('move', document, move, { passive: true }) + onPointer('up', document, handleUp, { passive: true }) } return { diff --git a/src/pointers.ts b/src/pointers.ts index 32b119c3..9f802f4c 100644 --- a/src/pointers.ts +++ b/src/pointers.ts @@ -12,8 +12,18 @@ function findEventIndex(pointers: PointerEvent[], event: PointerEvent) { return -1 } -export function addEvent(pointers: PointerEvent[], event: PointerEvent) { - const i = findEventIndex(pointers, event) +export function addPointer(pointers: PointerEvent[], event: PointerEvent) { + let i + // Add touches if applicable + if ((event as any).touches) { + i = 0 + for (const touch of (event as any).touches) { + touch.pointerId = i++ + addPointer(pointers, touch) + } + return + } + i = findEventIndex(pointers, event) // Update if already present if (i > -1) { pointers.splice(i, 1) @@ -21,7 +31,15 @@ export function addEvent(pointers: PointerEvent[], event: PointerEvent) { pointers.push(event) } -export function removeEvent(pointers: PointerEvent[], event: PointerEvent) { +export function removePointer(pointers: PointerEvent[], event: PointerEvent) { + // Add touches if applicable + if ((event as any).touches) { + // Remove all touches + while (pointers.length) { + pointers.pop() + } + return + } const i = findEventIndex(pointers, event) if (i > -1) { pointers.splice(i, 1) diff --git a/src/polyfills.js b/src/polyfills.js index 83ede1ca..fcf2e94a 100644 --- a/src/polyfills.js +++ b/src/polyfills.js @@ -2,3 +2,13 @@ if (window.NodeList && !NodeList.prototype.forEach) { NodeList.prototype.forEach = Array.prototype.forEach } +// Support: IE11 only +// CustomEvent is an object instead of a constructor +if (typeof window.CustomEvent !== 'function') { + window.CustomEvent = function CustomEvent(event, params) { + params = params || { bubbles: false, cancelable: false, detail: null } + var evt = document.createEvent('CustomEvent') + evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail) + return evt + } +} diff --git a/src/types.ts b/src/types.ts index 700bbb92..73a82864 100644 --- a/src/types.ts +++ b/src/types.ts @@ -27,10 +27,11 @@ interface MiscOptions { */ origin?: string /** - * Override the transform setter + * Override the transform setter. * This is exposed mostly so the user could * set other parts of a transform * aside from scale and translate. + * Default is defined in src/css.ts. * * ```js * // This example always sets a rotation @@ -43,10 +44,14 @@ interface MiscOptions { * ``` */ setTransform?: typeof setTransform - /** Values used to set the beginning transform */ - startX?: number /* Default: 0 */ - startY?: number /* Default: 0 */ - startScale?: number /* Default: 1 */ + /** Silence all events */ + silent?: boolean + /** X Value used to set the beginning transform */ + startX?: number + /** Y Value used to set the beginning transform */ + startY?: number + /** Scale used to set the beginning transform */ + startScale?: number /** Pass through any options like data */ [key: string]: any } @@ -92,7 +97,7 @@ interface ZoomOptions { minScale?: number /** The maximum scale when zooming */ maxScale?: number - /** The step affects the rate of zooming with a mouse wheel, pinching, or range element */ + /** The step affects zoom calculation when zooming with a mouse wheel, when pinch zooming, or when using zoomIn/zoomOut */ step?: number } @@ -102,7 +107,7 @@ type ZoomOnlyOptions = MiscOptions & ZoomOptions export { ZoomOnlyOptions as ZoomOptions } export type PanzoomOptions = PanOptions & ZoomOptions & MiscOptions -interface CurrentValues { +export interface CurrentValues { x: number y: number scale: number @@ -130,6 +135,11 @@ export interface PanzoomObject { * Reset the pan and zoom to startX, startY, and startScale. * Animates by default, ignoring the global option. * Pass `{ animate: false }` to override. + * + * ```js + * panzoom.reset() + * panzoom.reset({ animate: false }) + * ``` */ reset: (resetOptions?: PanzoomOptions) => CurrentValues /** Change options for the Panzoom instance */ @@ -146,15 +156,25 @@ export interface PanzoomObject { */ zoom: (scale: number, zoomOptions?: ZoomOptions) => CurrentValues /** - * Zoom in using the predetermined increment set in options + * Zoom in using the predetermined increment set in options. * Animates by default, ignoring the global option. * Pass `{ animate: false }` to override. + * + * ```js + * panzoom.zoomIn() + * panzoom.zoomIn({ animate: false }) + * ``` */ zoomIn: (zoomOptions?: ZoomOptions) => CurrentValues /** - * Zoom out using the predetermined increment set in options + * Zoom out using the predetermined increment set in options. * Animates by default, ignoring the global option. * Pass `{ animate: false }` to override. + * + * ```js + * panzoom.zoomOut() + * panzoom.zoomOut({ animate: false }) + * ``` */ zoomOut: (zoomOptions?: ZoomOptions) => CurrentValues /** @@ -162,6 +182,10 @@ export interface PanzoomObject { * the given pointer/touch/mouse event or constructed point. * The clientX/clientY values should be calculated * the same way as a pointer event on the Panzoom element. + * + * ```js + * panzoom.zoomToPoint(1.2, pointerEvent) + * ``` */ zoomToPoint: ( scale: number, @@ -184,11 +208,11 @@ export interface PanzoomObject { * * ```js * // Bind to mousewheel - * elem.parentElement.addEventListener('wheel', panzoom.zoomUsingWheel) + * elem.parentElement.addEventListener('wheel', panzoom.zoomWithWheel) * // Bind to shift+mousewheel * elem.parentElement.addEventListener('wheel', function(event) { * if (!event.shiftKey) return - * panzoom.zoomUsingWheel(event) + * panzoom.zoomWithWheel(event) * }) * ``` */ diff --git a/tasks/EVENTS.md b/tasks/EVENTS.md new file mode 100644 index 00000000..6e2f152a --- /dev/null +++ b/tasks/EVENTS.md @@ -0,0 +1,40 @@ +## Events + +The following events are available as custom events on the panzoom element using the native [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) API. +Add listeners the same way you would any other event. + +```js +elem.addEventListener('panzoomchange', (event) => { + console.log(event.detail) // => { x: 0, y: 0, scale: 1 } +}) +``` + +### Notes about all events + +- The event object passed as an argument to the listener will always have a `detail` property with the current `x`, `y`, and `scale` values. +- Events can be silenced when the `silent` option is set to `true`, either globally or when passed to `pan`, any `zoom` method, or `reset`. +- Avoid putting too much logic in these event handlers as it could effect the performance of panning or zooming. + +### `"panzoomstart"` + +Fired when the user starts a move or pinch zoom gesture on mobile. + +### `"panzoomchange"` + +Fired whenever there is a pan, zoom, or reset. Note that direct calls to `options.setTransform` do not fire this event. + +### `"panzoomzoom"` + +Fired whenever the zoom is changed by any Panzoom `zoom` method, directly or internally. + +### `"panzoompan"` + +Fired whenever the zoom is changed by the `pan` method, directly or internally. + +### `"panzoomend"` + +Fired when the user finishes a move or finishes a pinch zoom gesture on mobile. + +### `"panzoomreset"` + +Fired whenever reset is called. diff --git a/tasks/docs.js b/tasks/docs.js index 4e724fa0..2e35ab28 100644 --- a/tasks/docs.js +++ b/tasks/docs.js @@ -1,5 +1,6 @@ const fs = require('fs') const prettier = require('prettier') +const pkg = require('../package.json') function read(filename) { return fs.readFileSync(`${__dirname}/${filename}`, { encoding: 'utf8' }) } @@ -16,45 +17,66 @@ function redoLinks(data) { // Remove links that aren't links to source .replace(/\[([^:]+)\]\(.*?\)/g, '$1') .replace(/PanzoomOptions/g, '[PanzoomOptions](#PanzoomOptions)') + .replace(/PanOptions/g, '[PanOptions](#PanOptions)') + .replace(/ZoomOptions/g, '[ZoomOptions](#ZoomOptions)') .replace(/PanzoomObject/g, '[PanzoomObject](#PanzoomObject)') .replace(/CurrentValues/g, '[CurrentValues](#CurrentValues)') ) } -const constructor = read('../docs/modules/_panzoom_.md') +const [constructor, defaultOptions] = redoLinks(read('../docs/modules/_panzoom_.md')) // Remove unwanted text .replace(/[\w\W]+###\s*Panzoom/, '') .replace('## Object literals\n\n', '') .replace('### ▪ **defaultOptions**: *object*\n\n', '') -data += '\n\n### Default export\n\n' + redoLinks(constructor) + .split('### `Const` defaultOptions') +data += constructor +const parsedDefaults = {} +defaultOptions.replace(/\*\*(\w+)\*\*: \*\w+\* = (["\w-\.]+)/g, function(all, key, value) { + parsedDefaults[key] = value + return all +}) +const rProperties = /[\w\W]+##\s*Properties/ +const rOptional = /`Optional` /g const panzoomOptions = - read('../docs/interfaces/_types_.miscoptions.md') + '\n\n## `PanzoomOptions`\n\nIncludes `MiscOptions`, `PanOptions`, and `ZoomOptions`\n\n' + + redoLinks(read('../docs/interfaces/_types_.miscoptions.md')) // Remove unwanted text - .replace(/[\w\W]+##\s*Properties/, '\n\n---\n\n## `MiscOptions`\n') + - read('../docs/interfaces/_types_.panoptions.md') + .replace(rOptional, '') + .replace(rProperties, '\n\n---\n\n## `MiscOptions`\n') + + redoLinks(read('../docs/interfaces/_types_.panoptions.md')) // Remove unwanted text - .replace(/[\w\W]+##\s*Properties/, '\n\n---\n\n## `PanOptions`\n\nIncludes `MiscOptions`\n\n') + - read('../docs/interfaces/_types_.zoomoptions.md') + .replace(rOptional, '') + .replace(rProperties, '\n\n---\n\n## `PanOptions`\n\nIncludes `MiscOptions`\n\n') + + redoLinks(read('../docs/interfaces/_types_.zoomoptions.md')) // Remove unwanted text - .replace(/[\w\W]+##\s*Properties/, '\n\n---\n\n## `ZoomOptions`\n\nIncludes `MiscOptions`\n\n') -data += - '\n\n## `PanzoomOptions`\n\nIncludes `MiscOptions`, `PanOptions`, and `ZoomOptions`\n\n' + - redoLinks(panzoomOptions) + .replace(rOptional, '') + .replace(rProperties, '\n\n---\n\n## `ZoomOptions`\n\nIncludes `MiscOptions`\n\n') +data += panzoomOptions + // Add in default values to option descriptions + .replace(/\*\*(\w+)\*\*\??\s*: \*\w+\*/g, function(all, key) { + return parsedDefaults[key] ? `${all} (Default: **${parsedDefaults[key]}**)` : all + }) -const panzoomObject = read('../docs/interfaces/_types_.panzoomobject.md') - // Remove unwanted text - .replace(/[\w\W]+##\s*Properties/, '') - // Type declaration refers to the signature - .replace(/Type declaration:/g, 'Signature with return type:') -data += +const panzoomObject = '\n\n---\n\n## `PanzoomObject`\n\nThese methods are available after initializing Panzoom\n\n' + - redoLinks(panzoomObject) + redoLinks(read('../docs/interfaces/_types_.panzoomobject.md')) + // Remove unwanted text + .replace(rProperties, '') + // Type declaration refers to the signature + .replace(/Type declaration:/g, 'Signature with return type:') +data += panzoomObject + // Add parens to method names + .replace(/([^#])\#\#\#\s*(\w+)/g, '$1### $2()') const currentValues = read('../docs/interfaces/_types_.currentvalues.md') // Remove unwanted text - .replace(/[\w\W]+##\s*Properties/, '\n\n---\n\n## `CurrentValues`\n') -data += currentValues + .replace(rProperties, '\n\n---\n\n## `CurrentValues`\n') +data += currentValues + '\n' + +const events = read('./EVENTS.md') +data += events // Write a pretty version -write('../README.md', prettier.format(data, { parser: 'markdown', semi: false, singleQuote: true })) +write('../README.md', prettier.format(data, { ...pkg.prettier, parser: 'markdown' })) diff --git a/test/old_demo.html b/test/old_demo.html deleted file mode 100644 index 4d732eee..00000000 --- a/test/old_demo.html +++ /dev/null @@ -1,1657 +0,0 @@ - - - - Panzoom - - - - - -
-

Panzoom for panning and zooming elements using modern CSS

-
-
-

Panning and zooming

-
-
- -
-
-
- - - - -
- -
-
-
-

Pan only when zoomed

-
-
- -
-
-
- - - - -
- -
-
-
-

Containment within the parent element

-
- -
-
- - - - -
- -
-
-
-

Inverted containment within the parent element (to hide what's behind)

-
- -
-
- - - - -
- - -
-
-
-

- Automatically determine the best containment option based on the panzoom element's relation - to the parent -

-
- -
-
- - - - -
- -
-
-
-

Panning and zooming a rotated element

-
-
- -
-
-
- - - - -
- -
-
-
-

SVG support (move it all!)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- -
-
-
-

Using the $set option

- -
-
- -
-
- -
-
- -
-
-
- - - - -
- -
-
-
-

Pan only along the X axis (disableYAxis: true)

- -
-
- -
-
-
- - - - -
- -
-
-
-

Pan only along the Y axis (disableXAxis: true)

- -
-
- -
-
-
- - - - -
- -
-
-
-

Use the mousewheel to zoom on a focal point

-
-
- -
-
- -
- - diff --git a/yarn.lock b/yarn.lock index 67bf7d7e..7ec7f856 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1035,10 +1035,10 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^16.8.25": - version "16.8.25" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.25.tgz#0247613ab58b1b11ba10fed662e1947c5f2bb89c" - integrity sha512-ydAAkLnNTC4oYSxJ3zwK/4QcVmEecACJ4ZdxXITbxz/dhahBSDKY6OQ1uawAW6rE/7kfHccxulYLSAIZVrSq0A== +"@types/react@*": + version "16.9.1" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.1.tgz#862c83b4c9d5cd116e42fd9a4f3694843cd2c051" + integrity sha512-jGM2x8F7m7/r+81N/BOaUKVwbC5Cdw6ExlWEUpr77XPwVeNvAppnPEnMMLMfxRDYL8FPEX8MHjwtD2NQMJ0yyQ== dependencies: "@types/prop-types" "*" csstype "^2.2.0" @@ -2707,15 +2707,7 @@ conventional-commit-types@^2.0.0: resolved "https://registry.yarnpkg.com/conventional-commit-types/-/conventional-commit-types-2.1.1.tgz#352eb53f56fbc7c1a6c1ba059c2b6670c90b2a8a" integrity sha512-0Ts+fEdmjqYDOQ1yZ+LNgdSPO335XZw9qC10M7CxtLP3nIMGmeMhmkM8Taffa4+MXN13bRPlp0CtH+QfOzKTzw== -conventional-commits-filter@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-2.0.1.tgz#55a135de1802f6510b6758e0a6aa9e0b28618db3" - integrity sha512-92OU8pz/977udhBjgPEbg3sbYzIxMDFTlQT97w7KdhR9igNqdJvy8smmedAAgn4tPiqseFloKkrVfbXCVd+E7A== - dependencies: - is-subset "^0.1.1" - modify-values "^1.0.0" - -conventional-commits-filter@^2.0.2: +conventional-commits-filter@^2.0.0, conventional-commits-filter@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-2.0.2.tgz#f122f89fbcd5bb81e2af2fcac0254d062d1039c1" integrity sha512-WpGKsMeXfs21m1zIw4s9H5sys2+9JccTzpN6toXtxhpw2VNF2JUXwIakthKBy+LN4DvJm+TzWhxOMWOs1OFCFQ== @@ -4844,10 +4836,10 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -husky@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/husky/-/husky-3.0.2.tgz#e78fd2ae16edca59fc88e56aeb8d70acdcc1c082" - integrity sha512-WXCtaME2x0o4PJlKY4ap8BzLA+D0zlvefqAvLCPriOOu+x0dpO5uc5tlB7CY6/0SE2EESmoZsj4jW5D09KrJoA== +husky@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/husky/-/husky-3.0.3.tgz#6f3fb99f60ef72cdf34e5d78445c2f798c441b1d" + integrity sha512-DBBMPSiBYEMx7EVUTRE/ymXJa/lOL+WplcsV/lZu+/HHGt0gzD+5BIz9EJnCrWyUa7hkMuBh7/9OZ04qDkM+Nw== dependencies: chalk "^2.4.2" cosmiconfig "^5.2.1" @@ -4856,7 +4848,7 @@ husky@^3.0.2: is-ci "^2.0.0" opencollective-postinstall "^2.0.2" pkg-dir "^4.2.0" - please-upgrade-node "^3.1.1" + please-upgrade-node "^3.2.0" read-pkg "^5.1.1" run-node "^1.0.0" slash "^3.0.0" @@ -5427,11 +5419,6 @@ is-stream@^1.0.0, is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= -is-subset@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" - integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY= - is-symbol@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" @@ -7789,10 +7776,10 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -please-upgrade-node@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.1.1.tgz#ed320051dfcc5024fae696712c8288993595e8ac" - integrity sha512-KY1uHnQ2NlQHqIJQpnh/i54rKkuxCEBx+voJIS/Mvb+L2iYd2NMotwduhKTMjfC1uKoX3VXOxLjIYG66dfJTVQ== +please-upgrade-node@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" + integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg== dependencies: semver-compare "^1.0.0" @@ -8178,30 +8165,29 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-dom@^16.8.6: - version "16.8.6" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.6.tgz#71d6303f631e8b0097f56165ef608f051ff6e10f" - integrity sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA== +react-dom@^16.9.0: + version "16.9.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.9.0.tgz#5e65527a5e26f22ae3701131bcccaee9fb0d3962" + integrity sha512-YFT2rxO9hM70ewk9jq0y6sQk8cL02xm4+IzYBz75CQGlClQQ1Bxq0nhHF6OtSbit+AIahujJgb/CPRibFkMNJQ== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.13.6" + scheduler "^0.15.0" react-is@^16.8.1: version "16.8.6" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== -react@^16.8.6: - version "16.8.6" - resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe" - integrity sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw== +react@^16.9.0: + version "16.9.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.9.0.tgz#40ba2f9af13bc1a38d75dbf2f4359a5185c4f7aa" + integrity sha512-+7LQnFBwkiw+BobzOF6N//BdoNw0ouwmSJTEm9cglOOmsg/TMiFHZLe2sEoN5M7LgJTj9oHH0gxklfnQe66S1w== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.13.6" read-cmd-shim@^1.0.1, read-cmd-shim@~1.0.1: version "1.0.1" @@ -8854,10 +8840,10 @@ sax@^1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -scheduler@^0.13.6: - version "0.13.6" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889" - integrity sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ== +scheduler@^0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.15.0.tgz#6bfcf80ff850b280fed4aeecc6513bc0b4f17f8e" + integrity sha512-xAefmSfN6jqAa7Kuq7LIJY0bwAPG3xlCj0HMEBQk1lxYiDKZscY2xJ5U/61ZTrYbmNQbXa+gc7czPkVo11tnCg== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1"