├── dist
├── imgViewer.min.css
└── imgViewer.min.js
├── .gitignore
├── .github
└── FUNDING.yml
├── docs
├── images
│ └── test_image.jpg
├── dynamic_decorated_image.html
├── fixed_image_size.html
├── dynamic_image_size.html
├── custom_onclick_callback.html
├── multiple_viewers.html
├── control_options.html
└── lib
│ ├── imgViewer.js
│ └── hammer.min.js
├── src
├── .jshintrc
└── imgViewer.js
├── plugindocs
├── extending.md
├── usage.md
├── releases.md
├── methods.adoc
└── options.adoc
├── test
├── .jshintrc
├── imgViewer.html
├── imgViewer_options.js
└── imgViewer_methods.js
├── LICENSE-MIT
├── package.json
└── README.md
/dist/imgViewer.min.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .jshintrc
3 | .directory
4 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | ko_fi: waynegm
4 |
--------------------------------------------------------------------------------
/docs/images/test_image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/waynegm/imgViewer/HEAD/docs/images/test_image.jpg
--------------------------------------------------------------------------------
/src/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "curly": true,
3 | "eqeqeq": true,
4 | "immed": true,
5 | "latedef": true,
6 | "newcap": true,
7 | "noarg": true,
8 | "sub": true,
9 | "undef": true,
10 | "unused": true,
11 | "boss": true,
12 | "eqnull": true,
13 | "browser": true,
14 | "predef": ["jQuery"]
15 | }
16 |
--------------------------------------------------------------------------------
/plugindocs/extending.md:
--------------------------------------------------------------------------------
1 | # Extending
2 | The imgViewer widget is built using the [jQuery UI Widget Factory](https://learn.jquery.com/jquery-ui/widget-factory/). Extending the functionaity of the widget is a relatively simple process as described in [Extending Widgets with the Widget Factory](https://learn.jquery.com/jquery-ui/widget-factory/extending-widgets/).
3 |
4 | As an example
5 | ```javascript
6 | $.widget("wgm.imgExtension", $.wgm.imgViewer, {
7 | options: {
8 | }
9 | });
10 | ```
11 |
--------------------------------------------------------------------------------
/test/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "curly": true,
3 | "eqeqeq": true,
4 | "immed": true,
5 | "latedef": true,
6 | "newcap": true,
7 | "noarg": true,
8 | "sub": true,
9 | "undef": true,
10 | "unused": true,
11 | "boss": true,
12 | "eqnull": true,
13 | "browser": true,
14 | "predef": [
15 | "jQuery",
16 | "QUnit",
17 | "module",
18 | "test",
19 | "asyncTest",
20 | "expect",
21 | "start",
22 | "stop",
23 | "ok",
24 | "equal",
25 | "notEqual",
26 | "deepEqual",
27 | "notDeepEqual",
28 | "strictEqual",
29 | "notStrictEqual",
30 | "throws"
31 | ]
32 | }
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Wayne Mogg
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/test/imgViewer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | imgNotes Test Suite
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/plugindocs/usage.md:
--------------------------------------------------------------------------------
1 | #Using the Plugin
2 | ## Dependencies
3 | The plugin has the following dependencies:
4 | - [jQuery](http://jquery.com/) (>=1.8)
5 | - [jQuery UI](http://jqueryui.com/) (>=1.8)
6 | * [Widget Factory](http://api.jqueryui.com/jQuery.widget/)
7 | - [Hammer.js](http://hammerjs.github.io/) (>=2.0.8)
8 | - [jquery.hammer.js](https://github.com/hammerjs/jquery.hammer.js) (>=2.0.0)
9 | - [jquery-mousewheel](https://github.com/brandonaaron/jquery-mousewheel) (>=3.0)
10 |
11 | ## Usage
12 | Include either the development version or minified production version of the JS file located
13 | in the `dist` folder and associated dependencies into your web page:
14 |
15 | ```html
16 |
17 | ...
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | ...
26 |
27 | ```
28 | Put an image element and a javascript block to attach the plugin to the image in the web page body:
29 |
30 | ```html
31 |
32 | ...
33 |
34 | ...
35 |
42 | ...
43 |
44 | ```
45 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "imgviewer",
3 | "version": "1.0.1",
4 | "description": "extensible, responsive jquery plugin to zoom and pan images",
5 | "keywords": ["jquery-plugin", "ecosystem:jquery", "image"],
6 | "repository": {"type": "git", "url": "https://github.com/waynegm/imgViewer.git"},
7 | "homepage": "https://github.com/waynegm/imgViewer",
8 | "bugs": "https://github.com/waynegm/imgViewer/issues",
9 | "main": "src/imgViewer.js",
10 | "dependencies": {
11 | "jquery": "^1.8 || ^2.0 || ^3.0",
12 | "hammerjs": "^2.0.8",
13 | "jquery-hammerjs": "^2.0.0",
14 | "jquery-mousewheel": "^3.1.13"
15 | },
16 | "scripts": {
17 | "clean": "rimraf dist/*",
18 | "lint": "jshint src/*.js",
19 | "postlint": "cp src/*.js docs/lib",
20 | "minify:js": "echo '=> minify:js' && uglifyjs src/*.js -o dist/imgViewer.min.js",
21 | "minify:css": "echo '=> minify:css' && cleancss -o dist/imgViewer.min.css src/*.css",
22 | "prebuild:js": "npm run lint",
23 | "build:js": "npm run minify:js",
24 | "build:css": "npm run minify:css",
25 | "build": "echo '=> building' && npm run clean && npm run build:css && npm run build:js",
26 | "push": "git push",
27 | "patch-release": "npm version patch && npm publish",
28 | "minor-release": "npm version minor && npm publish",
29 | "major-release": "npm version major && npm publish",
30 | "watch": "watch 'npm run build' src/",
31 | "server": "http-server docs/"
32 | },
33 | "author": "waynegm",
34 | "license": "MIT",
35 | "devDependencies": {
36 | "clean-css-cli": "^4.0.0",
37 | "http-server": "^0.9.0",
38 | "jshint": "^2.9.4",
39 | "rimraf": "^2.5.4",
40 | "uglify-js": "^2.7.5",
41 | "watch": "^1.0.1"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/docs/dynamic_decorated_image.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | imgViewer Plugin - dynamic decorated image example
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | imgViewer Plugin - dynamic decorated image example
16 |
17 |
18 |
19 | This example demonstrates the widget with a decorated (borders and padding) dynamic width image.
20 | Zoom in and out using the mousewheel. Left mouseclick and drag to pan. On touch enable devices pinch gestures can be used to zoom in and out and tap and drag to pan around.
21 |
22 | Resize the window and try again
23 |
24 |
25 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | imgViewer
2 | =========
3 |
4 | imgViewer is a jQuery plugin that allows an image to be zoomed and panned. Zooming and panning works on desktop browsers using the mousewheel to zoom in and out left mouse button click and drag to pan around. On touch enabled devices pinch gestures can be used to zoom in and out and tap and drag to pan around.
5 |
6 | It is an easily extended, responsive UI component based on the jQuery widget factory and uses [Hammer.js](http://hammerjs.github.io/) for touch event support. It should work on all browsers and platforms supported by [Hammer.js](http://hammerjs.github.io/).
7 |
8 | If you don't find this plugin has what you need check out my [imgViewer2](https://github.com/waynegm/imgViewer2) plugin which provides the same functionality but uses the [Leaflet](http://leafletjs.com/index.html) mapping library for the underlying image display.
9 |
10 | ## Examples
11 | 1. [Fixed size image](http://waynegm.github.io/imgViewer/fixed_image_size.html)
12 | 2. [Dynamic image size](http://waynegm.github.io/imgViewer/dynamic_image_size.html)
13 | 3. [Decorated, dynamic image size](http://waynegm.github.io/imgViewer/dynamic_decorated_image.html)
14 | 4. [Custom onClick callback](http://waynegm.github.io/imgViewer/custom_onclick_callback.html)
15 | 5. [Control options](http://waynegm.github.io/imgViewer/control_options.html)
16 | 6. [Multiple viewer](http://waynegm.github.io/imgViewer/multiple_viewers.html)
17 |
18 | ## Documentation
19 | 1. [Using the plugin](plugindocs/usage.md)
20 | 2. [Plugin options](plugindocs/options.adoc)
21 | 3. [Plugin methods](plugindocs/methods.adoc)
22 | 4. [Extending the plugin](plugindocs/extending.md)
23 | 5. [Release History](releases.md)
24 |
25 | ## License
26 |
27 | This plugin is provided under the [MIT License](http://opensource.org/licenses/MIT).
28 | Copyright (c) 2017 Wayne Mogg.
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/docs/fixed_image_size.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | imgViewer Plugin - Fixed image size example
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | imgViewer Plugin - fixed image size example
19 |
20 |
21 |
22 | This example demonstrates the basic functionality for a plain fixed size image. Zoom in and out using the mousewheel. Left mouseclick and drag to pan. On touch enable devices pinch gestures can be used to zoom in and out and tap and drag to pan around.
23 |
24 |
25 |
26 |
27 |
28 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/docs/dynamic_image_size.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | imgViewer Plugin - dynamic image size example
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | imgViewer Plugin - dynamic image size example
20 |
21 |
22 |
23 | This example demonstrates the widget with a dynamic image width. Zoom in and out using the mousewheel. Left mouseclick and drag to pan. On touch enable devices pinch gestures can be used to zoom in and out and tap and drag to pan around.
24 |
25 | Resize the window and try again.
26 |
27 |
28 |
29 |
30 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/plugindocs/releases.md:
--------------------------------------------------------------------------------
1 | #Release History
2 | ## 1.0.1
3 | - replace call to img.load() event trigger depreciated in jQuery 3 with img.trigger("load")
4 | ## 1.0.0
5 | - Restructure repository layout
6 | - Change build system from grunt to vanilla npm
7 | - Edit package.json for npmjs package publishing
8 | - Move examples to docs folder for serving by ghpages
9 | - Revamp examples
10 | - Split documetation out of README.md and place in plugindocs folder
11 | - Update Hammerjs to latest version (2.0.8)
12 | - Fix pageX and pageY for IE11 tap events
13 | - Fix drag bug in Firefox
14 | - Improve behaviour of touch gestures
15 |
16 | ## 0.9.1
17 | - Add zoomMax option to limit maximum possible zoom level
18 |
19 | ## 0.9.0
20 | - Replace jquery.event.ue with hammer.js and jquery.hammer.js for more flexibility with touch gesture support
21 | - Add dragable option allowing user to disable dragging
22 |
23 | ## 0.8.0
24 | - Replace toe.js with jquery.event.ue for better touch gesture support
25 |
26 | ## 0.7.4
27 | - Fix for triggering of drag events during pinch gestures
28 |
29 | ## 0.7.3
30 | - Fix multiple click/tap events when using jQuery Mobile
31 |
32 | ## 0.7.2
33 | - Add dependency on jquery-mousewheel
34 | - Stop IE 10 & 11 continuously dragging image
35 |
36 | ## 0.7.1
37 | - Add zoomable option allowing user to disable zooming
38 | - Fix bug in drag implementation on mobile devices
39 |
40 | ## 0.7
41 | - Added support for pinch and drag touch gestures for mobile device support (adds requirement for toe.js).
42 | - Added dependency on the requestAnimationFrame polyfill provided by Zoetrope for more responsive image scaling and dragging.
43 | - Changed to using css transform to scale and translate image for better performance on mobile platforms.
44 | - Minimum IE supported is now IE 9 - stick with version 0.6 if you need IE 8 support.
45 | - Updated Grunfile.js to include tests against latest version (2.1.0) of jQuery.
46 |
47 | ## 0.6
48 | - Major refactoring of the code to make it work in IE8.
49 | - Instead of manipulating a background image a new image element with the same src as the original image is positioned over it.
50 | - Added the panTo, getView, isVisible, imgtoView and viewToImg public methods.
51 | - Added unit tests to cover most of the code.
52 |
53 | ## 0.5
54 | - Proof of concept - everything seems to work as I want but unit tests are needed and the exposed interface may need refinement to increase it's flexibility and usefulness.
55 |
--------------------------------------------------------------------------------
/test/imgViewer_options.js:
--------------------------------------------------------------------------------
1 | /*
2 | * imgViewer_options.js
3 | */
4 | (function($) {
5 | module("imgViewer: options");
6 |
7 | test( "zoom option", 3, function() {
8 | var $img = $("#qunit-fixture img");
9 | var zoom = 5;
10 | var tst = $img.imgViewer({
11 | zoom: zoom
12 | });
13 | equal(tst.imgViewer("option", "zoom"), zoom, "set zoom option in constructor");
14 | zoom = 2;
15 | tst.imgViewer("option", "zoom", zoom);
16 | equal(tst.imgViewer("option", "zoom"), zoom, "set zoom option on built object");
17 | tst.imgViewer("option", "zoom", 0.5);
18 | equal(tst.imgViewer("option", "zoom"), zoom, "no change if zoom is less than 1");
19 | tst.remove();
20 | });
21 |
22 | test( "zoomStep option", 3, function() {
23 | var $img = $("#qunit-fixture img");
24 | var step = 0.5;
25 | var tst = $img.imgViewer({
26 | zoomStep: step
27 | });
28 | equal(tst.imgViewer("option", "zoomStep"), step, "set zoomStep option in constructor");
29 | step = 0.25;
30 | tst.imgViewer("option", "zoomStep", step);
31 | equal(tst.imgViewer("option", "zoomStep"), step, "set zoomStep option on built object");
32 | tst.imgViewer("option", "zoomStep", -0.2);
33 | equal(tst.imgViewer("option", "zoomStep"), step, "no change if zoomStep is less than 0");
34 | tst.remove();
35 | });
36 |
37 | test( "zoomMax option", 3, function() {
38 | var $img = $("#qunit-fixture img");
39 | var zmax = 5;
40 | var tst = $img.imgViewer({
41 | zoomMax: zmax
42 | });
43 | equal(tst.imgViewer("option", "zoomMax"), zmax, "set zoomMax option in constructor");
44 | zmax = 4;
45 | tst.imgViewer("option", "zoomMax", zmax);
46 | equal(tst.imgViewer("option", "zoomMax"), zmax, "set zoomMax option on built object");
47 | tst.imgViewer("option", "zoom", 5);
48 | equal(tst.imgViewer("option", "zoom"), zmax, "zoom restricted by zoomMax");
49 | tst.remove();
50 | });
51 |
52 | test( "zoomable option", 2, function() {
53 | var $img = $("#qunit-fixture img");
54 | var zoom = false;
55 | var tst = $img.imgViewer({
56 | zoomable: zoom
57 | });
58 | equal(tst.imgViewer("option", "zoomable"), zoom, "set zoomable option in constructor");
59 | zoom = true;
60 | tst.imgViewer("option", "zoomable", zoom);
61 | equal(tst.imgViewer("option", "zoomable"), zoom, "set zoomable option on built object");
62 | tst.remove();
63 | });
64 |
65 | test( "dragable option", 2, function() {
66 | var $img = $("#qunit-fixture img");
67 | var drag = false;
68 | var tst = $img.imgViewer({
69 | dragable: drag
70 | });
71 | equal(tst.imgViewer("option", "dragable"), drag, "set dragable option in constructor");
72 | drag = true;
73 | tst.imgViewer("option", "dragable", drag);
74 | equal(tst.imgViewer("option", "dragable"), drag, "set dragable option on built object");
75 | tst.remove();
76 | });
77 |
78 |
79 | }(jQuery));
80 |
--------------------------------------------------------------------------------
/docs/custom_onclick_callback.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | imgViewer Plugin - custom onClick callback example
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | imgViewer Plugin - custom onClick callback example
18 |
19 |
20 |
21 |
22 | This example demonstrates the widget with a custom "onClick" callback. In this case clicking on the image will show the page pixel coordinates, image pixel coordinates and relative image coordinates (relative image coordinates range from 0,0 to 1,1 where 0,0 is the top-left corner and 1,1 is the bottom-right corner of the image) in a modal dialog.
23 |
24 |
25 | Zoom in and out using the mousewheel. Left mouseclick and drag to pan. On touch enable devices pinch gestures can be used to zoom in and out and tap and drag to pan around.
26 |
27 |
28 | Resize the window and try again
29 |
30 |
31 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/plugindocs/methods.adoc:
--------------------------------------------------------------------------------
1 | = Plugin Methods
2 | :toc:
3 | :toc-placement!:
4 |
5 | toc::[]
6 |
7 | == destroy - Destroys the plugin
8 | * No Arguments
9 | * No return
10 |
11 | == cursorToImg - Convert page pixel coordinates to relative image coordinate
12 | * Arguments:
13 | * pageX: x coordinate in pixel(page) coordinates
14 | * pageY: y coordiante in pixel(page) coordinates
15 | * Returns javascript object with relative image coordinates (relative image coordinates range from 0 to 1
16 | where 0,0 correspondes to the topleft corner and 1,1 the bottom right):
17 | * { x: relative x image coordinate, y: relative y image coordinate }
18 | * Returns null if the page coordinate is outside the image viewport.
19 |
20 | == getView - Get the relative image coordinates of the current view
21 | * Returns a javascript object with the relative image coordinates:
22 | * { top: minimum relative y coordinate,
23 | * left: minimum relative x coordinate,
24 | * bottom: maximum relative y image coordinate,
25 | * right: maximum relative x coordinate
26 | * }
27 |
28 | == imgToCursor - Convert relative image coordinate to a page pixel coordinate
29 | * Arguments:
30 | * relx: relative x image coordinate
31 | * rely: relative y image coordinate
32 | * Returns a javascript object with the page pixel coordinates:
33 | * { x: the x page pixel coordinate, y: the y page pixel coordinate }
34 | * Returns null if the relative image coordinates are not >=0 and <=1.
35 |
36 | == imgToView - Convert relative image coordinate to a viewport pixel location
37 | * Arguments:
38 | * relx: relative x image coordinate
39 | * rely: relative y image coordinate
40 | * Returns a javascript object with the viewport pixel coordinates:
41 | * { x: the x viewport pixel coordinate, y: the y viewport pixel coordinate }
42 | * Returns null if the relative image coordinates are not >=0 and <=1.
43 |
44 | == viewToImg - Convert a viewport pixel location to a relative image coordinate
45 | * Arguments:
46 | * viewX: x coordinate in viewport pixels
47 | * viewY: y coordinate in viewport pixels
48 | * Returns a javascript object with the relative image coordinates:
49 | * { x: relative x image coordinate, y: relative y image coordinate }
50 | * Returns null if the viewport pixel location is outside the zoomed image.
51 |
52 | == relposToImage - Convert relative image point to image pixel point
53 | * Arguments:
54 | * { x: relative x image coordinate, y: relative y image coordinate }
55 | * Returns a javascript object with the image pixel coordinates:
56 | * { x: the x image pixel coordinate, y: the y image pixel coordinate }
57 | *
58 | == isVisible - Is relative image coordinate visible
59 | * Arguments:
60 | * relx: relative x image coordinate
61 | * rely: relative y image coordinate
62 | * Returns
63 | * true or false
64 |
65 | == panTo - Pan to a relative image coordinates
66 | * Arguments:
67 | * relx: relative x image coordinate
68 | * rely: relative y image coordinate
69 | * Returns a javascript object with the relative image coordinates of the view centre after snapping the edges of the zoomed image to the view boundaries.
70 | * { x: view center relative x image coordinate, y: view center relative y image coordinate }
71 | * Returns null if the relative image coordinates are not >=0 and <=1 and the view is not changed.
72 |
73 | == update - Update the image display
74 | * No arguments
75 | * No return
76 |
--------------------------------------------------------------------------------
/plugindocs/options.adoc:
--------------------------------------------------------------------------------
1 | = Plugin Options
2 | :toc:
3 | :toc-placement!:
4 |
5 | The widget options can be set at the time of creation:
6 | [source, javascript]
7 | var $img = $("#image1").imgViewer({
8 | dragable: false,
9 | onClick: function( e, pos ) {
10 | $("#position").html("relx: " + pos.x + " rely: " + pos.y + " zoom: " + this.options.zoom() );
11 | }
12 | });
13 |
14 | or afterwards by:
15 | [source, javascript]
16 | $img.imgViewer("option", "zoomable", false);
17 |
18 | The current value of an option can be retrieved by:
19 | [source, javascript]
20 | $img.imgViewer("option", "zoomMax");
21 |
22 | toc::[]
23 | == dragable - Controls if image will be dragable
24 | * Default: true
25 | * Example - to disable image dragging:
26 |
27 | [source, javascript]
28 | $("#image1").imgViewer("option", "dragable", false);
29 |
30 | == zoomStep - controls zoom change for each mousewheel click
31 | * Default: 0.1
32 | * Example:
33 |
34 | [source, javascript]
35 | $("#image1").imgViewer("option", "zoomStep", 0.05);
36 |
37 | == zoom - Get/Set the current zoom level
38 | * Default: 1 (ie the entire image is visible)
39 | * Example - to display the image magnified 3x:
40 |
41 | [source, javascript]
42 | $("#image1").imgViewer("option", "zoom", 3);
43 |
44 | == zoomMax - Get/Set the zoom limitage
45 | * Default: 0 (ie no limit on zoom)
46 | * Note: values less than 1 have no affect
47 | * Example - to restrict zoom to 3x or less:
48 |
49 | [source, javascript]
50 | $("#image1").imgViewer("option", "zoomMax", 3);
51 |
52 | == zoomable - Controls if image will be zoomable
53 | * Default: true
54 | * Example - to disable image zooming:
55 |
56 | [source, javascript]
57 | $("#image1").imgViewer("option", "zoomable", false);
58 |
59 | == onClick - mouseclick callback on the image
60 | * Default: $.noop
61 | * Note: within the callback function `this` refers to the imgViewer object
62 | * Callback Arguments:
63 | * e: the original click event object
64 | * Example - to display the relative image coordinate clicked (relative image coordinates range from 0 to 1
65 | where 0,0 correspondes to the topleft corner and 1,1 the bottom right):
66 |
67 | [source, javascript]
68 | $("#image1").imgViewer("option", "onClick", function(e) {
69 | var pos = this.cursorToImg( e.pageX, e.pageY);
70 | $("#click_position").html(e.pageX + " " + e.pageY + " " + pos.x + " " + pos.y);
71 | });
72 |
73 | == onUpdate - Callback triggered after the image has been redisplayed
74 | * Default: $.noop
75 | * Note: within the callback function `this` refers to the imgViewer object
76 | * Callback Arguments:
77 | * None
78 | * Example - to display the relative image coordinate at the centre of the view:
79 |
80 | [source, javascript]
81 | $("#image1").imgViewer("option", "onUpdate", function() {
82 | var pos = {
83 | relx: this.vCenter.x/$(this.img).width(),
84 | rely: this.vCenter.y/$(this.img).height()
85 | };
86 | $("#centre_position").html(pos.relx + " " + pos.rely);
87 | });
88 |
89 | == onReady - Callback triggered when the plugin is fully initialised
90 | * Default: $.noop
91 | * Note: within the callback function `this` refers to the imgViewer object
92 | * Callback Arguments:
93 | * None
94 | * Example - to zoom and pan the image to a focal point on widget load:
95 |
96 | [source, javascript]
97 | $("#image1").imgViewer({
98 | onReady: function() {
99 | this.options.zoom = 3;
100 | this.panTo(1,1);
101 | }
102 | });
103 |
--------------------------------------------------------------------------------
/docs/multiple_viewers.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | imgViewer Plugin - Multi-viewer example
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | imgViewer Plugin - multi-viewer example
19 |
20 | No Container - Fixed Image Size
21 |
22 | Click Position
23 |
24 | No Container - Dynamic Width
25 |
26 | Click Position
27 |
28 | No Container - Fixed Size with Border and Padding
29 |
30 | Click Position
31 |
32 | Centred in a div - Dynamic Width
33 |
34 |
35 |
Click Position
36 |
37 |
38 |
39 |
40 |
41 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/docs/control_options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | imgViewer Plugin - control options
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | imgViewer Plugin - control options
19 |
20 |
21 |
22 |
Zoom On Drag On
23 |
24 | Zoom:
Maximum Zoom:
25 |
26 |
27 | This example demonstrates how the widget behaviour can be controlled by option settings.
28 |
29 |
30 | Zoom and drag can be enabled/disabled using the buttons.The maximum zoom level and the zoom can be set. In a custom onReady callback the intial zoom and center view are set using widget methods. A custom onUpdate callback is used to update the zoom value displayed.
31 |
32 |
33 |
34 |
35 |
36 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/dist/imgViewer.min.js:
--------------------------------------------------------------------------------
1 | var waitForFinalEvent=function(){var timers={};return function(callback,ms,uniqueId){if(!uniqueId){uniqueId="Don't call this twice without a uniqueId"}if(timers[uniqueId]){clearTimeout(timers[uniqueId])}timers[uniqueId]=setTimeout(callback,ms)}}();(function($){$.widget("wgm.imgViewer",{options:{zoomStep:.1,zoom:1,zoomMax:undefined,zoomable:true,dragable:true,onReady:$.noop,onClick:$.noop,onUpdate:$.noop},_create:function(){var self=this;if(!this.element.is("img")){$.error("imgviewer plugin can only be applied to img elements")}self.img=self.element[0];var $img=$(self.img);self.zimg=$(" ",{src:self.img.src}).appendTo("body").wrap("
");var $zimg=$(self.zimg);self.view=$(self.zimg).parent();var $view=$(self.view);self.vCenter={};self.drag=false;self.pinch=false;self.ready=false;$img.one("load",function(){self.ready=true;var width=$img.width(),height=$img.height(),offset=$img.offset();self.offsetPadding={top:parseInt($img.css("padding-top"),10),left:parseInt($img.css("padding-left"),10),right:parseInt($img.css("padding-right"),10),bottom:parseInt($img.css("padding-bottom"),10)};self.offsetBorder={x:Math.round(($img.outerWidth()-$img.innerWidth())/2),y:Math.round(($img.outerHeight()-$img.innerHeight())/2)};var vTop=offset.top+self.offsetBorder.y+self.offsetPadding.top,vLeft=offset.left+self.offsetBorder.x+self.offsetPadding.left;$view.css({position:"absolute",overflow:"hidden",top:vTop+"px",left:vLeft+"px",width:width+"px",height:height+"px"});$zimg.css({position:"relative",top:0+"px",left:0+"px",width:width+"px",height:height+"px","-webkit-tap-highlight-color":"transparent"});self.vCenter={x:width/2,y:height/2};self.update()}).each(function(){if(this.complete){$(this).trigger("load")}});self.render=false;$zimg.hammer();if(self.options.zoomable){self._bind_zoom_events()}if(self.options.dragable){self._bind_drag_events()}$zimg.on("tap",function(ev){ev.preventDefault();if(!self.dragging){var scoff=self._get_scroll_offset();ev.pageX=ev.gesture.center.x+scoff.x;ev.pageY=ev.gesture.center.y+scoff.y;self.options.onClick.call(self,ev)}});$(window).resize(function(){self._view_resize();waitForFinalEvent(function(){self._view_resize()},300,$img[0].id)});self._view_resize();self.options.onReady.call(self)},_get_scroll_offset:function(){var sx,sy;if(window.scrollX===undefined){if(window.pageXOffset===undefined){sx=document.documentElement.scrollLeft;sy=document.documentElement.scrollTop}else{sx=window.pageXOffset;sy=window.pageYOffset}}else{sx=window.scrollX;sy=window.scrollY}return{x:sx,y:sy}},_view_resize:function(){if(this.ready){var $view=$(this.view),$img=$(this.img),width=$img.width(),height=$img.height(),offset=$img.offset(),vTop=Math.round(offset.top+this.offsetBorder.y+this.offsetPadding.top),vLeft=Math.round(offset.left+this.offsetBorder.x+this.offsetPadding.left);this.vCenter.x*=$img.width()/$view.width();this.vCenter.y*=$img.height()/$view.height();$view.css({top:vTop+"px",left:vLeft+"px",width:width+"px",height:height+"px"});this.update()}},_bind_zoom_events:function(){var self=this;var $zimg=$(self.zimg);function doRender(){if(self.render){window.requestAnimationFrame(doRender);self.update()}}function startRenderLoop(){if(!self.render){self.render=true;doRender()}}function stopRenderLoop(){self.render=false}$zimg.on("mousewheel",function(ev){ev.preventDefault();var delta=ev.deltaY;self.options.zoom-=delta*self.options.zoomStep;self.update()});$zimg.on("touchmove",function(e){e.preventDefault()});$zimg.data("hammer").recognizers[1].options.enable=true;$zimg.on("pinchstart",function(){});$zimg.on("pinch",function(ev){ev.preventDefault();if(!self.pinch){var scoff=self._get_scroll_offset();self.pinchstart={x:ev.gesture.center.x+scoff.x,y:ev.gesture.center.y+scoff.y};self.pinchstartrelpos=self.cursorToImg(self.pinchstart.x,self.pinchstart.y);self.pinchstart_scale=self.options.zoom;startRenderLoop();self.pinch=true}else{self.options.zoom=ev.gesture.scale*self.pinchstart_scale;var npos=self.imgToCursor(self.pinchstartrelpos.x,self.pinchstartrelpos.y);self.vCenter.x=self.vCenter.x+(npos.x-self.pinchstart.x)/self.options.zoom;self.vCenter.y=self.vCenter.y+(npos.y-self.pinchstart.y)/self.options.zoom}});$zimg.on("pinchend",function(ev){ev.preventDefault();if(self.pinch){stopRenderLoop();self.update();self.pinch=false}})},_bind_drag_events:function(){var self=this;var $zimg=$(self.zimg);function doRender(){if(self.render){window.requestAnimationFrame(doRender);self.update()}}function startRenderLoop(){if(!self.render){self.render=true;doRender()}}function stopRenderLoop(){self.render=false}$zimg.on("mousedown",function(e){e.preventDefault()});$zimg.on("panstart",function(){});$zimg.on("panmove",function(ev){ev.preventDefault();if(!self.drag){self.drag=true;self.dragXorg=self.vCenter.x;self.dragYorg=self.vCenter.y;startRenderLoop()}else{self.vCenter.x=self.dragXorg-ev.gesture.deltaX/self.options.zoom;self.vCenter.y=self.dragYorg-ev.gesture.deltaY/self.options.zoom}});$zimg.on("panend",function(ev){ev.preventDefault();if(self.drag){self.drag=false;stopRenderLoop();self.update()}})},_unbind_zoom_events:function(){var self=this;var $zimg=$(self.zimg);$zimg.data("hammer").recognizers[1].options.enable=false;$zimg.off("mousewheel");$zimg.off("pinchstart");$zimg.off("pinch");$zimg.off("pinchend")},_unbind_drag_events:function(){var self=this;var $zimg=$(self.zimg);$zimg.off("pan");$zimg.off("panend")},destroy:function(){var $zimg=$(this.zimg);$zimg.unbind("click");$(window).unbind("resize");$zimg.remove();$(this.view).remove();$.Widget.prototype.destroy.call(this)},_setOption:function(key,value){switch(key){case"zoom":if(parseFloat(value)<1||isNaN(parseFloat(value))){return}break;case"zoomStep":if(parseFloat(value)<=0||isNaN(parseFloat(value))){return}break;case"zoomMax":if(parseFloat(value)<1||isNaN(parseFloat(value))){return}break}var version=$.ui.version.split(".");if(version[0]>1||version[1]>8){this._super(key,value)}else{$.Widget.prototype._setOption.apply(this,arguments)}switch(key){case"zoom":if(this.ready){this.update()}break;case"zoomable":if(this.options.zoomable){this._bind_zoom_events()}else{this._unbind_zoom_events()}break;case"dragable":if(this.options.dragable){this._bind_drag_events()}else{this._unbind_drag_events()}break;case"zoomMax":if(this.ready){this._view_resize();this.update()}break}},addElem:function(elem){$(this.view).append(elem)},isVisible:function(relx,rely){var view=this.getView();if(view){return relx>=view.left&&relx<=view.right&&rely>=view.top&&rely<=view.bottom}else{return false}},getView:function(){if(this.ready){var $img=$(this.img),width=$img.width(),height=$img.height(),zoom=this.options.zoom;return{top:this.vCenter.y/height-.5/zoom,left:this.vCenter.x/width-.5/zoom,bottom:this.vCenter.y/height+.5/zoom,right:this.vCenter.x/width+.5/zoom}}else{return null}},panTo:function(relx,rely){if(this.ready&&relx>=0&&relx<=1&&rely>=0&&rely<=1){var $img=$(this.img),width=$img.width(),height=$img.height();this.vCenter.x=relx*width;this.vCenter.y=rely*height;this.update();return{x:this.vCenter.x/width,y:this.vCenter.y/height}}else{return null}},imgToView:function(relx,rely){if(this.ready&&relx>=0&&relx<=1&&rely>=0&&rely<=1){var $img=$(this.img),width=$img.width(),height=$img.height();var zLeft=width/2-this.vCenter.x*this.options.zoom;var zTop=height/2-this.vCenter.y*this.options.zoom;var vx=relx*width*this.options.zoom+zLeft;var vy=rely*height*this.options.zoom+zTop;return{x:Math.round(vx),y:Math.round(vy)}}else{return null}},imgToCursor:function(relx,rely){var pos=this.imgToView(relx,rely);if(pos){var offset=$(this.img).offset();pos.x+=offset.left+this.offsetBorder.x+this.offsetPadding.left;pos.y+=offset.top+this.offsetBorder.y+this.offsetPadding.top;return pos}else{return null}},viewToImg:function(vx,vy){if(this.ready){var $img=$(this.img),width=$img.width(),height=$img.height();var zLeft=width/2-this.vCenter.x*this.options.zoom;var zTop=height/2-this.vCenter.y*this.options.zoom;var relx=(vx-zLeft)/(width*this.options.zoom);var rely=(vy-zTop)/(height*this.options.zoom);if(relx>=0&&relx<=1&&rely>=0&&rely<=1){return{x:relx,y:rely}}else{return null}}else{return null}},cursorToImg:function(cx,cy){if(this.ready){var $img=$(this.img),width=$img.width(),height=$img.height(),offset=$img.offset();var zLeft=width/2-this.vCenter.x*this.options.zoom;var zTop=height/2-this.vCenter.y*this.options.zoom;var relx=(cx-offset.left-this.offsetBorder.x-this.offsetPadding.left-zLeft)/(width*this.options.zoom);var rely=(cy-offset.top-this.offsetBorder.y-this.offsetPadding.top-zTop)/(height*this.options.zoom);if(relx>=0&&relx<=1&&rely>=0&&rely<=1){return{x:relx,y:rely}}else{return null}}else{return null}},relposToImage:function(pos){if(this.ready){var img=this.img,width=img.naturalWidth,height=img.naturalHeight;return{x:Math.round(pos.x*width),y:Math.round(pos.y*height)}}else{return null}},update:function(){if(this.ready){var zTop,zLeft,zWidth,zHeight,$img=$(this.img),width=$img.width(),height=$img.height(),zoom=this.options.zoom,zoomMax=this.options.zoomMax,half_width=width/2,half_height=height/2;zoom=zoomMax===undefined?zoom:Math.min(zoom,zoomMax);this.options.zoom=zoom;if(zoom<=1){zTop=0;zLeft=0;zWidth=width;zHeight=height;this.vCenter={x:half_width,y:half_height};this.options.zoom=1;zoom=1}else{zTop=Math.round(half_height-this.vCenter.y*zoom);zLeft=Math.round(half_width-this.vCenter.x*zoom);zWidth=Math.round(width*zoom);zHeight=Math.round(height*zoom);if(zLeft>0){this.vCenter.x=half_width/zoom;zLeft=0}else if(zLeft+zWidth0){this.vCenter.y=half_height/zoom;zTop=0}else if(zTop+zHeight ", {"src": self.img.src}).appendTo("body").wrap("
");
50 | var $zimg = $(self.zimg);
51 | // the container or viewport for the image view
52 | self.view = $(self.zimg).parent();
53 | var $view = $(self.view);
54 | // the pixel coordinate of the original image at the center of the viewport
55 | self.vCenter = {};
56 | // a flag used to decide if a mouse click is part of a drag or a proper click
57 | self.drag = false;
58 | self.pinch = false;
59 | // a flag used to check the target image has loaded
60 | self.ready = false;
61 | $img.one("load",function() {
62 | // get and some geometry information about the image
63 | self.ready = true;
64 | var width = $img.width(),
65 | height = $img.height(),
66 | offset = $img.offset();
67 | // cache the image padding information
68 | self.offsetPadding = {
69 | top: parseInt($img.css('padding-top'),10),
70 | left: parseInt($img.css('padding-left'),10),
71 | right: parseInt($img.css('padding-right'),10),
72 | bottom: parseInt($img.css('padding-bottom'),10)
73 | };
74 | /*
75 | * cache the image margin/border size information
76 | * because of IE8 limitations left and right borders are assumed to be the same width
77 | * and likewise top and bottom borders
78 | */
79 | self.offsetBorder = {
80 | x: Math.round(($img.outerWidth()-$img.innerWidth())/2),
81 | y: Math.round(($img.outerHeight()-$img.innerHeight())/2)
82 | };
83 | /*
84 | * define the css style for the view container using absolute positioning to
85 | * put it directly over the original image
86 | */
87 | var vTop = offset.top + self.offsetBorder.y + self.offsetPadding.top,
88 | vLeft = offset.left + self.offsetBorder.x + self.offsetPadding.left;
89 |
90 | $view.css({
91 | position: "absolute",
92 | overflow: "hidden",
93 | top: vTop+"px",
94 | left: vLeft+"px",
95 | width: width+"px",
96 | height: height+"px"
97 | });
98 | // the zoom and pan image is position relative to the view container
99 | $zimg.css({
100 | position: "relative",
101 | top: 0+"px",
102 | left: 0+"px",
103 | width: width+"px",
104 | height: height+"px",
105 | "-webkit-tap-highlight-color": "transparent"
106 | });
107 | // the initial view is centered at the orignal image
108 | self.vCenter = {
109 | x: width/2,
110 | y: height/2
111 | };
112 | self.update();
113 | }).each(function() {
114 | if (this.complete) { $(this).trigger("load"); }
115 | });
116 | /*
117 | * Render loop code during dragging and scaling using requestAnimationFrame
118 | */
119 | self.render = false;
120 | /*
121 | * Event handlers
122 | */
123 | $zimg.hammer();
124 |
125 | if (self.options.zoomable) {
126 | self._bind_zoom_events();
127 | }
128 | if (self.options.dragable) {
129 | self._bind_drag_events();
130 | }
131 | $zimg.on("tap", function(ev) {
132 | ev.preventDefault();
133 | if (!self.dragging) {
134 | var scoff = self._get_scroll_offset();
135 | ev.pageX = ev.gesture.center.x + scoff.x;
136 | ev.pageY = ev.gesture.center.y + scoff.y;
137 | self.options.onClick.call(self, ev);
138 | }
139 | });
140 | /*
141 | * Window resize handler
142 | */
143 |
144 | $(window).resize(function() {
145 | self._view_resize();
146 | waitForFinalEvent(function(){
147 | self._view_resize();
148 | }, 300, $img[0].id);
149 |
150 | });
151 | self._view_resize();
152 |
153 | self.options.onReady.call(self);
154 | },
155 | /*
156 | * Return the window scroll offset - required to convert Hammer.js event coords to page locations
157 | */
158 | _get_scroll_offset: function() {
159 | var sx,sy;
160 | if (window.scrollX === undefined) {
161 | if (window.pageXOffset === undefined) {
162 | sx = document.documentElement.scrollLeft;
163 | sy = document.documentElement.scrollTop;
164 | } else {
165 | sx = window.pageXOffset;
166 | sy = window.pageYOffset;
167 | }
168 | } else {
169 | sx = window.scrollX;
170 | sy = window.scrollY;
171 | }
172 | return {x: sx, y: sy};
173 | },
174 | /*
175 | * View resize - the aim is to keep the view centered on the same location in the original image
176 | */
177 | _view_resize: function() {
178 | if (this.ready) {
179 | var $view = $(this.view),
180 | $img = $(this.img),
181 | width = $img.width(),
182 | height = $img.height(),
183 | offset = $img.offset(),
184 | vTop = Math.round(offset.top + this.offsetBorder.y + this.offsetPadding.top),
185 | vLeft = Math.round(offset.left + this.offsetBorder.x + this.offsetPadding.left);
186 | this.vCenter.x *=$img.width()/$view.width();
187 | this.vCenter.y *= $img.height()/$view.height();
188 | $view.css({
189 | top: vTop+"px",
190 | left: vLeft+"px",
191 | width: width+"px",
192 | height: height+"px"
193 | });
194 |
195 | this.update();
196 | }
197 | },
198 | /*
199 | * Bind events
200 | */
201 | _bind_zoom_events: function() {
202 | var self = this;
203 | var $zimg = $(self.zimg);
204 |
205 | function doRender() {
206 | if (self.render) {
207 | window.requestAnimationFrame(doRender);
208 | self.update();
209 | }
210 | }
211 | function startRenderLoop() {
212 | if (!self.render) {
213 | self.render = true;
214 | doRender();
215 | }
216 | }
217 | function stopRenderLoop() {
218 | self.render = false;
219 | }
220 |
221 | $zimg.on("mousewheel", function(ev) {
222 | ev.preventDefault();
223 | var delta = ev.deltaY ;
224 | self.options.zoom -= delta * self.options.zoomStep;
225 | self.update();
226 | });
227 |
228 | $zimg.on("touchmove", function(e) {
229 | e.preventDefault();
230 | // e.stopPropagation();
231 | });
232 | $zimg.data("hammer").recognizers[1].options.enable = true;
233 |
234 | $zimg.on("pinchstart", function() {
235 | });
236 |
237 | $zimg.on("pinch", function(ev) {
238 | ev.preventDefault();
239 | if (!self.pinch) {
240 | var scoff = self._get_scroll_offset();
241 | self.pinchstart = { x: ev.gesture.center.x+scoff.x, y: ev.gesture.center.y+scoff.y};
242 | self.pinchstartrelpos = self.cursorToImg(self.pinchstart.x, self.pinchstart.y);
243 | self.pinchstart_scale = self.options.zoom;
244 | startRenderLoop();
245 | self.pinch = true;
246 | } else {
247 | self.options.zoom = ev.gesture.scale * self.pinchstart_scale;
248 | var npos = self.imgToCursor( self.pinchstartrelpos.x, self.pinchstartrelpos.y);
249 | self.vCenter.x = self.vCenter.x + (npos.x - self.pinchstart.x)/self.options.zoom;
250 | self.vCenter.y = self.vCenter.y + (npos.y - self.pinchstart.y)/self.options.zoom;
251 | }
252 | });
253 |
254 | $zimg.on("pinchend", function(ev) {
255 | ev.preventDefault();
256 | if (self.pinch) {
257 | stopRenderLoop();
258 | self.update();
259 | self.pinch = false;
260 | }
261 | });
262 | },
263 |
264 | _bind_drag_events: function() {
265 | var self = this;
266 | var $zimg = $(self.zimg);
267 | function doRender() {
268 | if (self.render) {
269 | window.requestAnimationFrame(doRender);
270 | self.update();
271 | }
272 | }
273 | function startRenderLoop() {
274 | if (!self.render) {
275 | self.render = true;
276 | doRender();
277 | }
278 | }
279 | function stopRenderLoop() {
280 | self.render = false;
281 | }
282 | $zimg.on("mousedown", function(e) {
283 | e.preventDefault();
284 | });
285 | $zimg.on("panstart", function() {
286 | });
287 |
288 | $zimg.on("panmove", function(ev) {
289 | ev.preventDefault();
290 | if (!self.drag) {
291 | self.drag = true;
292 | self.dragXorg = self.vCenter.x;
293 | self.dragYorg = self.vCenter.y;
294 | startRenderLoop();
295 | } else {
296 | self.vCenter.x = self.dragXorg - ev.gesture.deltaX/self.options.zoom;
297 | self.vCenter.y = self.dragYorg - ev.gesture.deltaY/self.options.zoom;
298 | }
299 | });
300 |
301 | $zimg.on( "panend", function(ev) {
302 | ev.preventDefault();
303 | if (self.drag) {
304 | self.drag = false;
305 | stopRenderLoop();
306 | self.update();
307 | }
308 | });
309 | },
310 | /*
311 | * Unbind events
312 | */
313 | _unbind_zoom_events: function() {
314 | var self = this;
315 | var $zimg = $(self.zimg);
316 | $zimg.data("hammer").recognizers[1].options.enable = false;
317 | $zimg.off("mousewheel");
318 | $zimg.off("pinchstart");
319 | $zimg.off("pinch");
320 | $zimg.off("pinchend");
321 | },
322 |
323 | _unbind_drag_events: function() {
324 | var self = this;
325 | var $zimg = $(self.zimg);
326 | $zimg.off("pan");
327 | $zimg.off("panend");
328 | },
329 |
330 | /*
331 | * Remove the plugin
332 | */
333 | destroy: function() {
334 | var $zimg = $(this.zimg);
335 | $zimg.unbind("click");
336 | $(window).unbind("resize");
337 | $zimg.remove();
338 | $(this.view).remove();
339 | $.Widget.prototype.destroy.call(this);
340 | },
341 |
342 | _setOption: function(key, value) {
343 | switch(key) {
344 | case 'zoom':
345 | if (parseFloat(value) < 1 || isNaN(parseFloat(value))) {
346 | return;
347 | }
348 | break;
349 | case 'zoomStep':
350 | if (parseFloat(value) <= 0 || isNaN(parseFloat(value))) {
351 | return;
352 | }
353 | break;
354 | case 'zoomMax':
355 | if (parseFloat(value) < 1 || isNaN(parseFloat(value))) {
356 | return;
357 | }
358 | break;
359 | }
360 | var version = $.ui.version.split('.');
361 | if (version[0] > 1 || version[1] > 8) {
362 | this._super(key, value);
363 | } else {
364 | $.Widget.prototype._setOption.apply(this, arguments);
365 | }
366 | switch(key) {
367 | case 'zoom':
368 | if (this.ready) {
369 | this.update();
370 | }
371 | break;
372 | case 'zoomable':
373 | if (this.options.zoomable) {
374 | this._bind_zoom_events();
375 | } else {
376 | this._unbind_zoom_events();
377 | }
378 | break;
379 | case 'dragable':
380 | if (this.options.dragable) {
381 | this._bind_drag_events();
382 | } else {
383 | this._unbind_drag_events();
384 | }
385 | break;
386 | case 'zoomMax':
387 | if (this.ready) {
388 | this._view_resize();
389 | this.update();
390 | }
391 | break;
392 | }
393 | },
394 |
395 | addElem: function(elem) {
396 | $(this.view).append(elem);
397 | },
398 | /*
399 | * Test if a relative image coordinate is visible in the current view
400 | */
401 | isVisible: function(relx, rely) {
402 | var view = this.getView();
403 | if (view) {
404 | return (relx >= view.left && relx <= view.right && rely >= view.top && rely <= view.bottom);
405 | } else {
406 | return false;
407 | }
408 | },
409 | /*
410 | * Get relative image coordinates of current view
411 | */
412 | getView: function() {
413 | if (this.ready) {
414 | var $img = $(this.img),
415 | width = $img.width(),
416 | height = $img.height(),
417 | zoom = this.options.zoom;
418 | return {
419 | top: this.vCenter.y/height - 0.5/zoom,
420 | left: this.vCenter.x/width - 0.5/zoom,
421 | bottom: this.vCenter.y/height + 0.5/zoom,
422 | right: this.vCenter.x/width + 0.5/zoom
423 | };
424 | } else {
425 | return null;
426 | }
427 | },
428 | /*
429 | * Pan the view to be centred at the given relative image location
430 | */
431 | panTo: function(relx, rely) {
432 | if ( this.ready && relx >= 0 && relx <= 1 && rely >= 0 && rely <=1 ) {
433 | var $img = $(this.img),
434 | width = $img.width(),
435 | height = $img.height();
436 | this.vCenter.x = relx * width;
437 | this.vCenter.y = rely * height;
438 | this.update();
439 | return { x: this.vCenter.x/width, y: this.vCenter.y/height };
440 | } else {
441 | return null;
442 | }
443 | },
444 | /*
445 | * Convert a relative image location to a viewport pixel location
446 | */
447 | imgToView: function(relx, rely) {
448 | if ( this.ready && relx >= 0 && relx <= 1 && rely >= 0 && rely <=1 ) {
449 | var $img = $(this.img),
450 | width = $img.width(),
451 | height = $img.height();
452 |
453 | var zLeft = width/2 - this.vCenter.x * this.options.zoom;
454 | var zTop = height/2 - this.vCenter.y * this.options.zoom;
455 | var vx = relx * width * this.options.zoom + zLeft;
456 | var vy = rely * height * this.options.zoom + zTop;
457 | return { x: Math.round(vx), y: Math.round(vy) };
458 | } else {
459 |
460 | return null;
461 | }
462 | },
463 | /*
464 | * Convert a relative image location to a page pixel location
465 | */
466 | imgToCursor: function(relx, rely) {
467 | var pos = this.imgToView(relx, rely);
468 | if (pos) {
469 | var offset = $(this.img).offset();
470 | pos.x += offset.left + this.offsetBorder.x + this.offsetPadding.left;
471 | pos.y += offset.top + this.offsetBorder.y + this.offsetPadding.top;
472 | return pos;
473 | } else {
474 | return null;
475 | }
476 | },
477 | /*
478 | * Convert a viewport pixel location to a relative image coordinate
479 | */
480 | viewToImg: function(vx, vy) {
481 | if (this.ready) {
482 | var $img = $(this.img),
483 | width = $img.width(),
484 | height = $img.height();
485 | var zLeft = width/2 - this.vCenter.x * this.options.zoom;
486 | var zTop = height/2 - this.vCenter.y * this.options.zoom;
487 | var relx= (vx - zLeft)/(width * this.options.zoom);
488 | var rely = (vy - zTop)/(height * this.options.zoom);
489 | if (relx>=0 && relx<=1 && rely>=0 && rely<=1) {
490 | return {x: relx, y: rely};
491 | } else {
492 | return null;
493 | }
494 | } else {
495 | return null;
496 | }
497 | },
498 |
499 | /*
500 | * Convert a page pixel location to a relative image coordinate
501 | */
502 | cursorToImg: function(cx, cy) {
503 | if (this.ready) {
504 | var $img = $(this.img),
505 | width = $img.width(),
506 | height = $img.height(),
507 | offset = $img.offset();
508 | var zLeft = width/2 - this.vCenter.x * this.options.zoom;
509 | var zTop = height/2 - this.vCenter.y * this.options.zoom;
510 | var relx = (cx - offset.left - this.offsetBorder.x - this.offsetPadding.left- zLeft)/(width * this.options.zoom);
511 | var rely = (cy - offset.top - this.offsetBorder.y - this.offsetPadding.top - zTop)/(height * this.options.zoom);
512 | if (relx>=0 && relx<=1 && rely>=0 && rely<=1) {
513 | return {x: relx, y: rely};
514 | } else {
515 | return null;
516 | }
517 | } else {
518 | return null;
519 | }
520 | },
521 | /*
522 | * Convert relative image coordinate to Image pixel
523 | */
524 | relposToImage: function(pos) {
525 | if (this.ready) {
526 | var img = this.img,
527 | width = img.naturalWidth,
528 | height = img.naturalHeight;
529 | return {x: Math.round(pos.x*width), y: Math.round(pos.y*height)};
530 | } else {
531 | return null;
532 | }
533 | },
534 | /*
535 | * Adjust the display of the image
536 | */
537 | update: function() {
538 | if (this.ready) {
539 | var zTop, zLeft, zWidth, zHeight,
540 | $img = $(this.img),
541 | width = $img.width(),
542 | height = $img.height(),
543 | // offset = $img.offset(),
544 | zoom = this.options.zoom,
545 | zoomMax = this.options.zoomMax,
546 | half_width = width/2,
547 | half_height = height/2;
548 |
549 | zoom = zoomMax === undefined ? zoom : Math.min(zoom, zoomMax);
550 | this.options.zoom = zoom;
551 |
552 | if (zoom <= 1) {
553 | zTop = 0;
554 | zLeft = 0;
555 | zWidth = width;
556 | zHeight = height;
557 | this.vCenter = {
558 | x: half_width,
559 | y: half_height
560 | };
561 | this.options.zoom = 1;
562 | zoom = 1;
563 | } else {
564 | zTop = Math.round(half_height - this.vCenter.y * zoom);
565 | zLeft = Math.round(half_width - this.vCenter.x * zoom);
566 | zWidth = Math.round(width * zoom);
567 | zHeight = Math.round(height * zoom);
568 | /*
569 | * adjust the view center so the image edges snap to the edge of the view
570 | */
571 | if (zLeft > 0) {
572 | this.vCenter.x = half_width/zoom;
573 | zLeft = 0;
574 | } else if (zLeft+zWidth < width) {
575 | this.vCenter.x = width - half_width/zoom ;
576 | zLeft = width - zWidth;
577 | }
578 | if (zTop > 0) {
579 | this.vCenter.y = half_height/zoom;
580 | zTop = 0;
581 | } else if (zTop + zHeight < height) {
582 | this.vCenter.y = height - half_height/zoom;
583 | zTop = height - zHeight;
584 | }
585 | }
586 | $(this.zimg).css({
587 | width: width+"px",
588 | height: height+"px"
589 | });
590 |
591 | var xt = -(this.vCenter.x - half_width)*zoom;
592 | var yt = -(this.vCenter.y - half_height)*zoom;
593 | $(this.zimg).css({transform: "translate(" + xt + "px," + yt + "px) scale(" + zoom + "," + zoom + ")" });
594 | /*
595 | * define the onUpdate option to do something after the image is redisplayed
596 | * probably shouldn't pass out the this object - need to think of something better
597 | */
598 | this.options.onUpdate.call(this);
599 | }
600 | }
601 | });
602 | })(jQuery);
603 |
--------------------------------------------------------------------------------
/docs/lib/imgViewer.js:
--------------------------------------------------------------------------------
1 | /*
2 | * imgViewer
3 | *
4 | *
5 | * Copyright (c) 2013 Wayne Mogg
6 | * Licensed under the MIT license.
7 | */
8 |
9 | var waitForFinalEvent = (function () {
10 | var timers = {};
11 | return function (callback, ms, uniqueId) {
12 | if (!uniqueId) {
13 | uniqueId = "Don't call this twice without a uniqueId";
14 | }
15 | if (timers[uniqueId]) {
16 | clearTimeout (timers[uniqueId]);
17 | }
18 | timers[uniqueId] = setTimeout(callback, ms);
19 | };
20 | })();
21 | /*
22 | * imgViewer plugin starts here
23 | */
24 | ;(function($) {
25 | $.widget("wgm.imgViewer", {
26 | options: {
27 | zoomStep: 0.1,
28 | zoom: 1,
29 | zoomMax: undefined,
30 | zoomable: true,
31 | dragable: true,
32 | onReady: $.noop,
33 | onClick: $.noop,
34 | onUpdate: $.noop
35 | },
36 |
37 | _create: function() {
38 | var self = this;
39 | if (!this.element.is("img")) {
40 | $.error('imgviewer plugin can only be applied to img elements');
41 | }
42 | // the original img element
43 | self.img = self.element[0];
44 | var $img = $(self.img);
45 | /*
46 | * a copy of the original image to be positioned over it and manipulated to
47 | * provide zoom and pan
48 | */
49 | self.zimg = $(" ", {"src": self.img.src}).appendTo("body").wrap("
");
50 | var $zimg = $(self.zimg);
51 | // the container or viewport for the image view
52 | self.view = $(self.zimg).parent();
53 | var $view = $(self.view);
54 | // the pixel coordinate of the original image at the center of the viewport
55 | self.vCenter = {};
56 | // a flag used to decide if a mouse click is part of a drag or a proper click
57 | self.drag = false;
58 | self.pinch = false;
59 | // a flag used to check the target image has loaded
60 | self.ready = false;
61 | $img.one("load",function() {
62 | // get and some geometry information about the image
63 | self.ready = true;
64 | var width = $img.width(),
65 | height = $img.height(),
66 | offset = $img.offset();
67 | // cache the image padding information
68 | self.offsetPadding = {
69 | top: parseInt($img.css('padding-top'),10),
70 | left: parseInt($img.css('padding-left'),10),
71 | right: parseInt($img.css('padding-right'),10),
72 | bottom: parseInt($img.css('padding-bottom'),10)
73 | };
74 | /*
75 | * cache the image margin/border size information
76 | * because of IE8 limitations left and right borders are assumed to be the same width
77 | * and likewise top and bottom borders
78 | */
79 | self.offsetBorder = {
80 | x: Math.round(($img.outerWidth()-$img.innerWidth())/2),
81 | y: Math.round(($img.outerHeight()-$img.innerHeight())/2)
82 | };
83 | /*
84 | * define the css style for the view container using absolute positioning to
85 | * put it directly over the original image
86 | */
87 | var vTop = offset.top + self.offsetBorder.y + self.offsetPadding.top,
88 | vLeft = offset.left + self.offsetBorder.x + self.offsetPadding.left;
89 |
90 | $view.css({
91 | position: "absolute",
92 | overflow: "hidden",
93 | top: vTop+"px",
94 | left: vLeft+"px",
95 | width: width+"px",
96 | height: height+"px"
97 | });
98 | // the zoom and pan image is position relative to the view container
99 | $zimg.css({
100 | position: "relative",
101 | top: 0+"px",
102 | left: 0+"px",
103 | width: width+"px",
104 | height: height+"px",
105 | "-webkit-tap-highlight-color": "transparent"
106 | });
107 | // the initial view is centered at the orignal image
108 | self.vCenter = {
109 | x: width/2,
110 | y: height/2
111 | };
112 | self.update();
113 | }).each(function() {
114 | if (this.complete) { $(this).trigger("load"); }
115 | });
116 | /*
117 | * Render loop code during dragging and scaling using requestAnimationFrame
118 | */
119 | self.render = false;
120 | /*
121 | * Event handlers
122 | */
123 | $zimg.hammer();
124 |
125 | if (self.options.zoomable) {
126 | self._bind_zoom_events();
127 | }
128 | if (self.options.dragable) {
129 | self._bind_drag_events();
130 | }
131 | $zimg.on("tap", function(ev) {
132 | ev.preventDefault();
133 | if (!self.dragging) {
134 | var scoff = self._get_scroll_offset();
135 | ev.pageX = ev.gesture.center.x + scoff.x;
136 | ev.pageY = ev.gesture.center.y + scoff.y;
137 | self.options.onClick.call(self, ev);
138 | }
139 | });
140 | /*
141 | * Window resize handler
142 | */
143 |
144 | $(window).resize(function() {
145 | self._view_resize();
146 | waitForFinalEvent(function(){
147 | self._view_resize();
148 | }, 300, $img[0].id);
149 |
150 | });
151 | self._view_resize();
152 |
153 | self.options.onReady.call(self);
154 | },
155 | /*
156 | * Return the window scroll offset - required to convert Hammer.js event coords to page locations
157 | */
158 | _get_scroll_offset: function() {
159 | var sx,sy;
160 | if (window.scrollX === undefined) {
161 | if (window.pageXOffset === undefined) {
162 | sx = document.documentElement.scrollLeft;
163 | sy = document.documentElement.scrollTop;
164 | } else {
165 | sx = window.pageXOffset;
166 | sy = window.pageYOffset;
167 | }
168 | } else {
169 | sx = window.scrollX;
170 | sy = window.scrollY;
171 | }
172 | return {x: sx, y: sy};
173 | },
174 | /*
175 | * View resize - the aim is to keep the view centered on the same location in the original image
176 | */
177 | _view_resize: function() {
178 | if (this.ready) {
179 | var $view = $(this.view),
180 | $img = $(this.img),
181 | width = $img.width(),
182 | height = $img.height(),
183 | offset = $img.offset(),
184 | vTop = Math.round(offset.top + this.offsetBorder.y + this.offsetPadding.top),
185 | vLeft = Math.round(offset.left + this.offsetBorder.x + this.offsetPadding.left);
186 | this.vCenter.x *=$img.width()/$view.width();
187 | this.vCenter.y *= $img.height()/$view.height();
188 | $view.css({
189 | top: vTop+"px",
190 | left: vLeft+"px",
191 | width: width+"px",
192 | height: height+"px"
193 | });
194 |
195 | this.update();
196 | }
197 | },
198 | /*
199 | * Bind events
200 | */
201 | _bind_zoom_events: function() {
202 | var self = this;
203 | var $zimg = $(self.zimg);
204 |
205 | function doRender() {
206 | if (self.render) {
207 | window.requestAnimationFrame(doRender);
208 | self.update();
209 | }
210 | }
211 | function startRenderLoop() {
212 | if (!self.render) {
213 | self.render = true;
214 | doRender();
215 | }
216 | }
217 | function stopRenderLoop() {
218 | self.render = false;
219 | }
220 |
221 | $zimg.on("mousewheel", function(ev) {
222 | ev.preventDefault();
223 | var delta = ev.deltaY ;
224 | self.options.zoom -= delta * self.options.zoomStep;
225 | self.update();
226 | });
227 |
228 | $zimg.on("touchmove", function(e) {
229 | e.preventDefault();
230 | // e.stopPropagation();
231 | });
232 | $zimg.data("hammer").recognizers[1].options.enable = true;
233 |
234 | $zimg.on("pinchstart", function() {
235 | });
236 |
237 | $zimg.on("pinch", function(ev) {
238 | ev.preventDefault();
239 | if (!self.pinch) {
240 | var scoff = self._get_scroll_offset();
241 | self.pinchstart = { x: ev.gesture.center.x+scoff.x, y: ev.gesture.center.y+scoff.y};
242 | self.pinchstartrelpos = self.cursorToImg(self.pinchstart.x, self.pinchstart.y);
243 | self.pinchstart_scale = self.options.zoom;
244 | startRenderLoop();
245 | self.pinch = true;
246 | } else {
247 | self.options.zoom = ev.gesture.scale * self.pinchstart_scale;
248 | var npos = self.imgToCursor( self.pinchstartrelpos.x, self.pinchstartrelpos.y);
249 | self.vCenter.x = self.vCenter.x + (npos.x - self.pinchstart.x)/self.options.zoom;
250 | self.vCenter.y = self.vCenter.y + (npos.y - self.pinchstart.y)/self.options.zoom;
251 | }
252 | });
253 |
254 | $zimg.on("pinchend", function(ev) {
255 | ev.preventDefault();
256 | if (self.pinch) {
257 | stopRenderLoop();
258 | self.update();
259 | self.pinch = false;
260 | }
261 | });
262 | },
263 |
264 | _bind_drag_events: function() {
265 | var self = this;
266 | var $zimg = $(self.zimg);
267 | function doRender() {
268 | if (self.render) {
269 | window.requestAnimationFrame(doRender);
270 | self.update();
271 | }
272 | }
273 | function startRenderLoop() {
274 | if (!self.render) {
275 | self.render = true;
276 | doRender();
277 | }
278 | }
279 | function stopRenderLoop() {
280 | self.render = false;
281 | }
282 | $zimg.on("mousedown", function(e) {
283 | e.preventDefault();
284 | });
285 | $zimg.on("panstart", function() {
286 | });
287 |
288 | $zimg.on("panmove", function(ev) {
289 | ev.preventDefault();
290 | if (!self.drag) {
291 | self.drag = true;
292 | self.dragXorg = self.vCenter.x;
293 | self.dragYorg = self.vCenter.y;
294 | startRenderLoop();
295 | } else {
296 | self.vCenter.x = self.dragXorg - ev.gesture.deltaX/self.options.zoom;
297 | self.vCenter.y = self.dragYorg - ev.gesture.deltaY/self.options.zoom;
298 | }
299 | });
300 |
301 | $zimg.on( "panend", function(ev) {
302 | ev.preventDefault();
303 | if (self.drag) {
304 | self.drag = false;
305 | stopRenderLoop();
306 | self.update();
307 | }
308 | });
309 | },
310 | /*
311 | * Unbind events
312 | */
313 | _unbind_zoom_events: function() {
314 | var self = this;
315 | var $zimg = $(self.zimg);
316 | $zimg.data("hammer").recognizers[1].options.enable = false;
317 | $zimg.off("mousewheel");
318 | $zimg.off("pinchstart");
319 | $zimg.off("pinch");
320 | $zimg.off("pinchend");
321 | },
322 |
323 | _unbind_drag_events: function() {
324 | var self = this;
325 | var $zimg = $(self.zimg);
326 | $zimg.off("pan");
327 | $zimg.off("panend");
328 | },
329 |
330 | /*
331 | * Remove the plugin
332 | */
333 | destroy: function() {
334 | var $zimg = $(this.zimg);
335 | $zimg.unbind("click");
336 | $(window).unbind("resize");
337 | $zimg.remove();
338 | $(this.view).remove();
339 | $.Widget.prototype.destroy.call(this);
340 | },
341 |
342 | _setOption: function(key, value) {
343 | switch(key) {
344 | case 'zoom':
345 | if (parseFloat(value) < 1 || isNaN(parseFloat(value))) {
346 | return;
347 | }
348 | break;
349 | case 'zoomStep':
350 | if (parseFloat(value) <= 0 || isNaN(parseFloat(value))) {
351 | return;
352 | }
353 | break;
354 | case 'zoomMax':
355 | if (parseFloat(value) < 1 || isNaN(parseFloat(value))) {
356 | return;
357 | }
358 | break;
359 | }
360 | var version = $.ui.version.split('.');
361 | if (version[0] > 1 || version[1] > 8) {
362 | this._super(key, value);
363 | } else {
364 | $.Widget.prototype._setOption.apply(this, arguments);
365 | }
366 | switch(key) {
367 | case 'zoom':
368 | if (this.ready) {
369 | this.update();
370 | }
371 | break;
372 | case 'zoomable':
373 | if (this.options.zoomable) {
374 | this._bind_zoom_events();
375 | } else {
376 | this._unbind_zoom_events();
377 | }
378 | break;
379 | case 'dragable':
380 | if (this.options.dragable) {
381 | this._bind_drag_events();
382 | } else {
383 | this._unbind_drag_events();
384 | }
385 | break;
386 | case 'zoomMax':
387 | if (this.ready) {
388 | this._view_resize();
389 | this.update();
390 | }
391 | break;
392 | }
393 | },
394 |
395 | addElem: function(elem) {
396 | $(this.view).append(elem);
397 | },
398 | /*
399 | * Test if a relative image coordinate is visible in the current view
400 | */
401 | isVisible: function(relx, rely) {
402 | var view = this.getView();
403 | if (view) {
404 | return (relx >= view.left && relx <= view.right && rely >= view.top && rely <= view.bottom);
405 | } else {
406 | return false;
407 | }
408 | },
409 | /*
410 | * Get relative image coordinates of current view
411 | */
412 | getView: function() {
413 | if (this.ready) {
414 | var $img = $(this.img),
415 | width = $img.width(),
416 | height = $img.height(),
417 | zoom = this.options.zoom;
418 | return {
419 | top: this.vCenter.y/height - 0.5/zoom,
420 | left: this.vCenter.x/width - 0.5/zoom,
421 | bottom: this.vCenter.y/height + 0.5/zoom,
422 | right: this.vCenter.x/width + 0.5/zoom
423 | };
424 | } else {
425 | return null;
426 | }
427 | },
428 | /*
429 | * Pan the view to be centred at the given relative image location
430 | */
431 | panTo: function(relx, rely) {
432 | if ( this.ready && relx >= 0 && relx <= 1 && rely >= 0 && rely <=1 ) {
433 | var $img = $(this.img),
434 | width = $img.width(),
435 | height = $img.height();
436 | this.vCenter.x = relx * width;
437 | this.vCenter.y = rely * height;
438 | this.update();
439 | return { x: this.vCenter.x/width, y: this.vCenter.y/height };
440 | } else {
441 | return null;
442 | }
443 | },
444 | /*
445 | * Convert a relative image location to a viewport pixel location
446 | */
447 | imgToView: function(relx, rely) {
448 | if ( this.ready && relx >= 0 && relx <= 1 && rely >= 0 && rely <=1 ) {
449 | var $img = $(this.img),
450 | width = $img.width(),
451 | height = $img.height();
452 |
453 | var zLeft = width/2 - this.vCenter.x * this.options.zoom;
454 | var zTop = height/2 - this.vCenter.y * this.options.zoom;
455 | var vx = relx * width * this.options.zoom + zLeft;
456 | var vy = rely * height * this.options.zoom + zTop;
457 | return { x: Math.round(vx), y: Math.round(vy) };
458 | } else {
459 |
460 | return null;
461 | }
462 | },
463 | /*
464 | * Convert a relative image location to a page pixel location
465 | */
466 | imgToCursor: function(relx, rely) {
467 | var pos = this.imgToView(relx, rely);
468 | if (pos) {
469 | var offset = $(this.img).offset();
470 | pos.x += offset.left + this.offsetBorder.x + this.offsetPadding.left;
471 | pos.y += offset.top + this.offsetBorder.y + this.offsetPadding.top;
472 | return pos;
473 | } else {
474 | return null;
475 | }
476 | },
477 | /*
478 | * Convert a viewport pixel location to a relative image coordinate
479 | */
480 | viewToImg: function(vx, vy) {
481 | if (this.ready) {
482 | var $img = $(this.img),
483 | width = $img.width(),
484 | height = $img.height();
485 | var zLeft = width/2 - this.vCenter.x * this.options.zoom;
486 | var zTop = height/2 - this.vCenter.y * this.options.zoom;
487 | var relx= (vx - zLeft)/(width * this.options.zoom);
488 | var rely = (vy - zTop)/(height * this.options.zoom);
489 | if (relx>=0 && relx<=1 && rely>=0 && rely<=1) {
490 | return {x: relx, y: rely};
491 | } else {
492 | return null;
493 | }
494 | } else {
495 | return null;
496 | }
497 | },
498 |
499 | /*
500 | * Convert a page pixel location to a relative image coordinate
501 | */
502 | cursorToImg: function(cx, cy) {
503 | if (this.ready) {
504 | var $img = $(this.img),
505 | width = $img.width(),
506 | height = $img.height(),
507 | offset = $img.offset();
508 | var zLeft = width/2 - this.vCenter.x * this.options.zoom;
509 | var zTop = height/2 - this.vCenter.y * this.options.zoom;
510 | var relx = (cx - offset.left - this.offsetBorder.x - this.offsetPadding.left- zLeft)/(width * this.options.zoom);
511 | var rely = (cy - offset.top - this.offsetBorder.y - this.offsetPadding.top - zTop)/(height * this.options.zoom);
512 | if (relx>=0 && relx<=1 && rely>=0 && rely<=1) {
513 | return {x: relx, y: rely};
514 | } else {
515 | return null;
516 | }
517 | } else {
518 | return null;
519 | }
520 | },
521 | /*
522 | * Convert relative image coordinate to Image pixel
523 | */
524 | relposToImage: function(pos) {
525 | if (this.ready) {
526 | var img = this.img,
527 | width = img.naturalWidth,
528 | height = img.naturalHeight;
529 | return {x: Math.round(pos.x*width), y: Math.round(pos.y*height)};
530 | } else {
531 | return null;
532 | }
533 | },
534 | /*
535 | * Adjust the display of the image
536 | */
537 | update: function() {
538 | if (this.ready) {
539 | var zTop, zLeft, zWidth, zHeight,
540 | $img = $(this.img),
541 | width = $img.width(),
542 | height = $img.height(),
543 | // offset = $img.offset(),
544 | zoom = this.options.zoom,
545 | zoomMax = this.options.zoomMax,
546 | half_width = width/2,
547 | half_height = height/2;
548 |
549 | zoom = zoomMax === undefined ? zoom : Math.min(zoom, zoomMax);
550 | this.options.zoom = zoom;
551 |
552 | if (zoom <= 1) {
553 | zTop = 0;
554 | zLeft = 0;
555 | zWidth = width;
556 | zHeight = height;
557 | this.vCenter = {
558 | x: half_width,
559 | y: half_height
560 | };
561 | this.options.zoom = 1;
562 | zoom = 1;
563 | } else {
564 | zTop = Math.round(half_height - this.vCenter.y * zoom);
565 | zLeft = Math.round(half_width - this.vCenter.x * zoom);
566 | zWidth = Math.round(width * zoom);
567 | zHeight = Math.round(height * zoom);
568 | /*
569 | * adjust the view center so the image edges snap to the edge of the view
570 | */
571 | if (zLeft > 0) {
572 | this.vCenter.x = half_width/zoom;
573 | zLeft = 0;
574 | } else if (zLeft+zWidth < width) {
575 | this.vCenter.x = width - half_width/zoom ;
576 | zLeft = width - zWidth;
577 | }
578 | if (zTop > 0) {
579 | this.vCenter.y = half_height/zoom;
580 | zTop = 0;
581 | } else if (zTop + zHeight < height) {
582 | this.vCenter.y = height - half_height/zoom;
583 | zTop = height - zHeight;
584 | }
585 | }
586 | $(this.zimg).css({
587 | width: width+"px",
588 | height: height+"px"
589 | });
590 |
591 | var xt = -(this.vCenter.x - half_width)*zoom;
592 | var yt = -(this.vCenter.y - half_height)*zoom;
593 | $(this.zimg).css({transform: "translate(" + xt + "px," + yt + "px) scale(" + zoom + "," + zoom + ")" });
594 | /*
595 | * define the onUpdate option to do something after the image is redisplayed
596 | * probably shouldn't pass out the this object - need to think of something better
597 | */
598 | this.options.onUpdate.call(this);
599 | }
600 | }
601 | });
602 | })(jQuery);
603 |
--------------------------------------------------------------------------------
/docs/lib/hammer.min.js:
--------------------------------------------------------------------------------
1 | /*! Hammer.JS - v2.0.8 - 2016-04-23
2 | * http://hammerjs.github.io/
3 | *
4 | * Copyright (c) 2016 Jorik Tangelder;
5 | * Licensed under the MIT license */
6 | !function(a,b,c,d){"use strict";function e(a,b,c){return setTimeout(j(a,c),b)}function f(a,b,c){return Array.isArray(a)?(g(a,c[b],c),!0):!1}function g(a,b,c){var e;if(a)if(a.forEach)a.forEach(b,c);else if(a.length!==d)for(e=0;e\s*\(/gm,"{anonymous}()@"):"Unknown Stack Trace",f=a.console&&(a.console.warn||a.console.log);return f&&f.call(a.console,e,d),b.apply(this,arguments)}}function i(a,b,c){var d,e=b.prototype;d=a.prototype=Object.create(e),d.constructor=a,d._super=e,c&&la(d,c)}function j(a,b){return function(){return a.apply(b,arguments)}}function k(a,b){return typeof a==oa?a.apply(b?b[0]||d:d,b):a}function l(a,b){return a===d?b:a}function m(a,b,c){g(q(b),function(b){a.addEventListener(b,c,!1)})}function n(a,b,c){g(q(b),function(b){a.removeEventListener(b,c,!1)})}function o(a,b){for(;a;){if(a==b)return!0;a=a.parentNode}return!1}function p(a,b){return a.indexOf(b)>-1}function q(a){return a.trim().split(/\s+/g)}function r(a,b,c){if(a.indexOf&&!c)return a.indexOf(b);for(var d=0;dc[b]}):d.sort()),d}function u(a,b){for(var c,e,f=b[0].toUpperCase()+b.slice(1),g=0;g1&&!c.firstMultiple?c.firstMultiple=D(b):1===e&&(c.firstMultiple=!1);var f=c.firstInput,g=c.firstMultiple,h=g?g.center:f.center,i=b.center=E(d);b.timeStamp=ra(),b.deltaTime=b.timeStamp-f.timeStamp,b.angle=I(h,i),b.distance=H(h,i),B(c,b),b.offsetDirection=G(b.deltaX,b.deltaY);var j=F(b.deltaTime,b.deltaX,b.deltaY);b.overallVelocityX=j.x,b.overallVelocityY=j.y,b.overallVelocity=qa(j.x)>qa(j.y)?j.x:j.y,b.scale=g?K(g.pointers,d):1,b.rotation=g?J(g.pointers,d):0,b.maxPointers=c.prevInput?b.pointers.length>c.prevInput.maxPointers?b.pointers.length:c.prevInput.maxPointers:b.pointers.length,C(c,b);var k=a.element;o(b.srcEvent.target,k)&&(k=b.srcEvent.target),b.target=k}function B(a,b){var c=b.center,d=a.offsetDelta||{},e=a.prevDelta||{},f=a.prevInput||{};b.eventType!==Ea&&f.eventType!==Ga||(e=a.prevDelta={x:f.deltaX||0,y:f.deltaY||0},d=a.offsetDelta={x:c.x,y:c.y}),b.deltaX=e.x+(c.x-d.x),b.deltaY=e.y+(c.y-d.y)}function C(a,b){var c,e,f,g,h=a.lastInterval||b,i=b.timeStamp-h.timeStamp;if(b.eventType!=Ha&&(i>Da||h.velocity===d)){var j=b.deltaX-h.deltaX,k=b.deltaY-h.deltaY,l=F(i,j,k);e=l.x,f=l.y,c=qa(l.x)>qa(l.y)?l.x:l.y,g=G(j,k),a.lastInterval=b}else c=h.velocity,e=h.velocityX,f=h.velocityY,g=h.direction;b.velocity=c,b.velocityX=e,b.velocityY=f,b.direction=g}function D(a){for(var b=[],c=0;ce;)c+=a[e].clientX,d+=a[e].clientY,e++;return{x:pa(c/b),y:pa(d/b)}}function F(a,b,c){return{x:b/a||0,y:c/a||0}}function G(a,b){return a===b?Ia:qa(a)>=qa(b)?0>a?Ja:Ka:0>b?La:Ma}function H(a,b,c){c||(c=Qa);var d=b[c[0]]-a[c[0]],e=b[c[1]]-a[c[1]];return Math.sqrt(d*d+e*e)}function I(a,b,c){c||(c=Qa);var d=b[c[0]]-a[c[0]],e=b[c[1]]-a[c[1]];return 180*Math.atan2(e,d)/Math.PI}function J(a,b){return I(b[1],b[0],Ra)+I(a[1],a[0],Ra)}function K(a,b){return H(b[0],b[1],Ra)/H(a[0],a[1],Ra)}function L(){this.evEl=Ta,this.evWin=Ua,this.pressed=!1,x.apply(this,arguments)}function M(){this.evEl=Xa,this.evWin=Ya,x.apply(this,arguments),this.store=this.manager.session.pointerEvents=[]}function N(){this.evTarget=$a,this.evWin=_a,this.started=!1,x.apply(this,arguments)}function O(a,b){var c=s(a.touches),d=s(a.changedTouches);return b&(Ga|Ha)&&(c=t(c.concat(d),"identifier",!0)),[c,d]}function P(){this.evTarget=bb,this.targetIds={},x.apply(this,arguments)}function Q(a,b){var c=s(a.touches),d=this.targetIds;if(b&(Ea|Fa)&&1===c.length)return d[c[0].identifier]=!0,[c,c];var e,f,g=s(a.changedTouches),h=[],i=this.target;if(f=c.filter(function(a){return o(a.target,i)}),b===Ea)for(e=0;e-1&&d.splice(a,1)};setTimeout(e,cb)}}function U(a){for(var b=a.srcEvent.clientX,c=a.srcEvent.clientY,d=0;d=f&&db>=g)return!0}return!1}function V(a,b){this.manager=a,this.set(b)}function W(a){if(p(a,jb))return jb;var b=p(a,kb),c=p(a,lb);return b&&c?jb:b||c?b?kb:lb:p(a,ib)?ib:hb}function X(){if(!fb)return!1;var b={},c=a.CSS&&a.CSS.supports;return["auto","manipulation","pan-y","pan-x","pan-x pan-y","none"].forEach(function(d){b[d]=c?a.CSS.supports("touch-action",d):!0}),b}function Y(a){this.options=la({},this.defaults,a||{}),this.id=v(),this.manager=null,this.options.enable=l(this.options.enable,!0),this.state=nb,this.simultaneous={},this.requireFail=[]}function Z(a){return a&sb?"cancel":a&qb?"end":a&pb?"move":a&ob?"start":""}function $(a){return a==Ma?"down":a==La?"up":a==Ja?"left":a==Ka?"right":""}function _(a,b){var c=b.manager;return c?c.get(a):a}function aa(){Y.apply(this,arguments)}function ba(){aa.apply(this,arguments),this.pX=null,this.pY=null}function ca(){aa.apply(this,arguments)}function da(){Y.apply(this,arguments),this._timer=null,this._input=null}function ea(){aa.apply(this,arguments)}function fa(){aa.apply(this,arguments)}function ga(){Y.apply(this,arguments),this.pTime=!1,this.pCenter=!1,this._timer=null,this._input=null,this.count=0}function ha(a,b){return b=b||{},b.recognizers=l(b.recognizers,ha.defaults.preset),new ia(a,b)}function ia(a,b){this.options=la({},ha.defaults,b||{}),this.options.inputTarget=this.options.inputTarget||a,this.handlers={},this.session={},this.recognizers=[],this.oldCssProps={},this.element=a,this.input=y(this),this.touchAction=new V(this,this.options.touchAction),ja(this,!0),g(this.options.recognizers,function(a){var b=this.add(new a[0](a[1]));a[2]&&b.recognizeWith(a[2]),a[3]&&b.requireFailure(a[3])},this)}function ja(a,b){var c=a.element;if(c.style){var d;g(a.options.cssProps,function(e,f){d=u(c.style,f),b?(a.oldCssProps[d]=c.style[d],c.style[d]=e):c.style[d]=a.oldCssProps[d]||""}),b||(a.oldCssProps={})}}function ka(a,c){var d=b.createEvent("Event");d.initEvent(a,!0,!0),d.gesture=c,c.target.dispatchEvent(d)}var la,ma=["","webkit","Moz","MS","ms","o"],na=b.createElement("div"),oa="function",pa=Math.round,qa=Math.abs,ra=Date.now;la="function"!=typeof Object.assign?function(a){if(a===d||null===a)throw new TypeError("Cannot convert undefined or null to object");for(var b=Object(a),c=1;ch&&(b.push(a),h=b.length-1):e&(Ga|Ha)&&(c=!0),0>h||(b[h]=a,this.callback(this.manager,e,{pointers:b,changedPointers:[a],pointerType:f,srcEvent:a}),c&&b.splice(h,1))}});var Za={touchstart:Ea,touchmove:Fa,touchend:Ga,touchcancel:Ha},$a="touchstart",_a="touchstart touchmove touchend touchcancel";i(N,x,{handler:function(a){var b=Za[a.type];if(b===Ea&&(this.started=!0),this.started){var c=O.call(this,a,b);b&(Ga|Ha)&&c[0].length-c[1].length===0&&(this.started=!1),this.callback(this.manager,b,{pointers:c[0],changedPointers:c[1],pointerType:za,srcEvent:a})}}});var ab={touchstart:Ea,touchmove:Fa,touchend:Ga,touchcancel:Ha},bb="touchstart touchmove touchend touchcancel";i(P,x,{handler:function(a){var b=ab[a.type],c=Q.call(this,a,b);c&&this.callback(this.manager,b,{pointers:c[0],changedPointers:c[1],pointerType:za,srcEvent:a})}});var cb=2500,db=25;i(R,x,{handler:function(a,b,c){var d=c.pointerType==za,e=c.pointerType==Ba;if(!(e&&c.sourceCapabilities&&c.sourceCapabilities.firesTouchEvents)){if(d)S.call(this,b,c);else if(e&&U.call(this,c))return;this.callback(a,b,c)}},destroy:function(){this.touch.destroy(),this.mouse.destroy()}});var eb=u(na.style,"touchAction"),fb=eb!==d,gb="compute",hb="auto",ib="manipulation",jb="none",kb="pan-x",lb="pan-y",mb=X();V.prototype={set:function(a){a==gb&&(a=this.compute()),fb&&this.manager.element.style&&mb[a]&&(this.manager.element.style[eb]=a),this.actions=a.toLowerCase().trim()},update:function(){this.set(this.manager.options.touchAction)},compute:function(){var a=[];return g(this.manager.recognizers,function(b){k(b.options.enable,[b])&&(a=a.concat(b.getTouchAction()))}),W(a.join(" "))},preventDefaults:function(a){var b=a.srcEvent,c=a.offsetDirection;if(this.manager.session.prevented)return void b.preventDefault();var d=this.actions,e=p(d,jb)&&!mb[jb],f=p(d,lb)&&!mb[lb],g=p(d,kb)&&!mb[kb];if(e){var h=1===a.pointers.length,i=a.distance<2,j=a.deltaTime<250;if(h&&i&&j)return}return g&&f?void 0:e||f&&c&Na||g&&c&Oa?this.preventSrc(b):void 0},preventSrc:function(a){this.manager.session.prevented=!0,a.preventDefault()}};var nb=1,ob=2,pb=4,qb=8,rb=qb,sb=16,tb=32;Y.prototype={defaults:{},set:function(a){return la(this.options,a),this.manager&&this.manager.touchAction.update(),this},recognizeWith:function(a){if(f(a,"recognizeWith",this))return this;var b=this.simultaneous;return a=_(a,this),b[a.id]||(b[a.id]=a,a.recognizeWith(this)),this},dropRecognizeWith:function(a){return f(a,"dropRecognizeWith",this)?this:(a=_(a,this),delete this.simultaneous[a.id],this)},requireFailure:function(a){if(f(a,"requireFailure",this))return this;var b=this.requireFail;return a=_(a,this),-1===r(b,a)&&(b.push(a),a.requireFailure(this)),this},dropRequireFailure:function(a){if(f(a,"dropRequireFailure",this))return this;a=_(a,this);var b=r(this.requireFail,a);return b>-1&&this.requireFail.splice(b,1),this},hasRequireFailures:function(){return this.requireFail.length>0},canRecognizeWith:function(a){return!!this.simultaneous[a.id]},emit:function(a){function b(b){c.manager.emit(b,a)}var c=this,d=this.state;qb>d&&b(c.options.event+Z(d)),b(c.options.event),a.additionalEvent&&b(a.additionalEvent),d>=qb&&b(c.options.event+Z(d))},tryEmit:function(a){return this.canEmit()?this.emit(a):void(this.state=tb)},canEmit:function(){for(var a=0;af?Ja:Ka,c=f!=this.pX,d=Math.abs(a.deltaX)):(e=0===g?Ia:0>g?La:Ma,c=g!=this.pY,d=Math.abs(a.deltaY))),a.direction=e,c&&d>b.threshold&&e&b.direction},attrTest:function(a){return aa.prototype.attrTest.call(this,a)&&(this.state&ob||!(this.state&ob)&&this.directionTest(a))},emit:function(a){this.pX=a.deltaX,this.pY=a.deltaY;var b=$(a.direction);b&&(a.additionalEvent=this.options.event+b),this._super.emit.call(this,a)}}),i(ca,aa,{defaults:{event:"pinch",threshold:0,pointers:2},getTouchAction:function(){return[jb]},attrTest:function(a){return this._super.attrTest.call(this,a)&&(Math.abs(a.scale-1)>this.options.threshold||this.state&ob)},emit:function(a){if(1!==a.scale){var b=a.scale<1?"in":"out";a.additionalEvent=this.options.event+b}this._super.emit.call(this,a)}}),i(da,Y,{defaults:{event:"press",pointers:1,time:251,threshold:9},getTouchAction:function(){return[hb]},process:function(a){var b=this.options,c=a.pointers.length===b.pointers,d=a.distanceb.time;if(this._input=a,!d||!c||a.eventType&(Ga|Ha)&&!f)this.reset();else if(a.eventType&Ea)this.reset(),this._timer=e(function(){this.state=rb,this.tryEmit()},b.time,this);else if(a.eventType&Ga)return rb;return tb},reset:function(){clearTimeout(this._timer)},emit:function(a){this.state===rb&&(a&&a.eventType&Ga?this.manager.emit(this.options.event+"up",a):(this._input.timeStamp=ra(),this.manager.emit(this.options.event,this._input)))}}),i(ea,aa,{defaults:{event:"rotate",threshold:0,pointers:2},getTouchAction:function(){return[jb]},attrTest:function(a){return this._super.attrTest.call(this,a)&&(Math.abs(a.rotation)>this.options.threshold||this.state&ob)}}),i(fa,aa,{defaults:{event:"swipe",threshold:10,velocity:.3,direction:Na|Oa,pointers:1},getTouchAction:function(){return ba.prototype.getTouchAction.call(this)},attrTest:function(a){var b,c=this.options.direction;return c&(Na|Oa)?b=a.overallVelocity:c&Na?b=a.overallVelocityX:c&Oa&&(b=a.overallVelocityY),this._super.attrTest.call(this,a)&&c&a.offsetDirection&&a.distance>this.options.threshold&&a.maxPointers==this.options.pointers&&qa(b)>this.options.velocity&&a.eventType&Ga},emit:function(a){var b=$(a.offsetDirection);b&&this.manager.emit(this.options.event+b,a),this.manager.emit(this.options.event,a)}}),i(ga,Y,{defaults:{event:"tap",pointers:1,taps:1,interval:300,time:250,threshold:9,posThreshold:10},getTouchAction:function(){return[ib]},process:function(a){var b=this.options,c=a.pointers.length===b.pointers,d=a.distance 1");
140 | equal(tst.imgViewer("imgToCursor", 0.5, -0.5), null, "returns null for relative y image coordinate < 0");
141 | equal(tst.imgViewer("imgToCursor", 0.5, 2.5), null, "returns null for relative x image coordinate > 1");
142 | tst.remove();
143 | start();
144 | }).each(function() {
145 | if (this.complete) { $(this).load(); }
146 | });
147 | });
148 |
149 | asyncTest( "imgToCursor at 4x zoom",3, function() {
150 | var $img = $("#qunit-fixture img");
151 | var zoom = 4;
152 | var tst = $img.imgViewer({zoom: zoom});
153 | $img.load(function() {
154 | var width = $img.width();
155 | var height = $img.height();
156 | var offset = $img.offset();
157 | var erelx = 0.5-0.5/zoom,
158 | erely = 0.5-0.5/zoom;
159 | var res = tst.imgViewer("imgToCursor", erelx, erely);
160 | ok(res.x === offset.left && res.y === offset.top, "top left corner at " + zoom + "x zoom is " + res.x + " " + res.y + " expected " + offset.left + " " + offset.top);
161 | erelx = 0.5+0.5/zoom;
162 | erely = 0.5+0.5/zoom;
163 | res = tst.imgViewer("imgToCursor", erelx, erely);
164 | ok(res.x === (offset.left+width) && res.y === (offset.top+height), "bottom right corner at " + zoom + "x zoom is " + res.x + " " + res.y + " expected " + (offset.left+width) + " " + (offset.top+height));
165 | erelx = 0.5-0.25/zoom;
166 | erely = 0.5-0.25/zoom;
167 | res = tst.imgViewer("imgToCursor", erelx, erely);
168 | ok(res.x === (offset.left+width/4) && res.y === (offset.top+height/4), "internal point at " + zoom + "x zoom is " + res.x + " " + res.y + " expected " + (offset.left+width/4) + " " + (offset.top+height/4));
169 | tst.remove();
170 | start();
171 | }).each(function() {
172 | if (this.complete) { $(this).load(); }
173 | });
174 | });
175 |
176 | asyncTest( "imgToCursor at 1x zoom with padding and border",1, function() {
177 | var $img = $("#qunit-fixture img");
178 | var zoom = 1;
179 | $img.css({border: "30px solid #ccc", padding: "20px"});
180 | var tst = $img.imgViewer({zoom: zoom});
181 | $img.load(function() {
182 | var offset = $img.offset();
183 | var erelx = 0.5-0.5/zoom,
184 | erely = 0.5-0.5/zoom;
185 | var res = tst.imgViewer("imgToCursor", erelx, erely);
186 | ok(res.x === offset.left+50 && res.y === offset.top+50, "top left corner at " + zoom + "x zoom is " + res.x + " " + res.y + " expected " + (offset.left+50) + " " + (offset.top+50));
187 | tst.remove();
188 | start();
189 | }).each(function() {
190 | if (this.complete) { $(this).load(); }
191 | });
192 | });
193 |
194 | asyncTest( "imgToCursor at 4x zoom with padding and border",1, function() {
195 | var $img = $("#qunit-fixture img");
196 | var zoom = 4;
197 | $img.css({border: "30px solid #ccc", padding: "20px"});
198 | var tst = $img.imgViewer({zoom: zoom});
199 | $img.load(function() {
200 | var offset = $img.offset();
201 | var erelx = 0.5-0.5/zoom,
202 | erely = 0.5-0.5/zoom;
203 | var res = tst.imgViewer("imgToCursor", erelx, erely);
204 | ok(res.x === offset.left+50 && res.y === offset.top+50, "top left corner at " + zoom + "x zoom is " + res.x + " " + res.y + " expected " + (offset.left+50) + " " + (offset.top+50));
205 | tst.remove();
206 | start();
207 | }).each(function() {
208 | if (this.complete) { $(this).load(); }
209 | });
210 | });
211 |
212 |
213 | asyncTest( "imgToView at 1x zoom",7, function() {
214 | var $img = $("#qunit-fixture img");
215 | var tst = $img.imgViewer();
216 | $img.load(function() {
217 | var width = $img.width();
218 | var height = $img.height();
219 | var res = tst.imgViewer("imgToView", 0.0, 0.0);
220 | ok(res.x === 0 && res.y === 0, "top left corner at 1x zoom is " + res.x + " " + res.y + " expected 0 0");
221 | res = tst.imgViewer("imgToView", 1.0, 1.0);
222 | ok(res.x === width && res.y === height, "bottom right corner at 1x zoom is " + res.x + " " + res.y + " expected " + width + " " + height);
223 | res = tst.imgViewer("imgToView", 0.25, 0.25);
224 | ok(res.x === Math.round(width/4) && res.y === Math.round(height/4), "internal point at 1x zoom is " + res.x + " " + res.y + " expected " + Math.round(width/4) + " " + Math.round(height/4));
225 | equal(tst.imgViewer("imgToView", -1.0, 0.5), null, "returns null for relative x image coordinate < 0");
226 | equal(tst.imgViewer("imgToView", 2.0, 0.5), null, "returns null for relative x image coordinate > 1");
227 | equal(tst.imgViewer("imgToView", 0.5, -0.5), null, "returns null for relative y image coordinate < 0");
228 | equal(tst.imgViewer("imgToView", 0.5, 2.5), null, "returns null for relative x image coordinate > 1");
229 | tst.remove();
230 | start();
231 | }).each(function() {
232 | if (this.complete) { $(this).load(); }
233 | });
234 | });
235 |
236 | asyncTest( "imgToView at 4x zoom",3, function() {
237 | var $img = $("#qunit-fixture img");
238 | var zoom = 4;
239 | var tst = $img.imgViewer({zoom: zoom});
240 | $img.load(function() {
241 | var width = $img.width();
242 | var height = $img.height();
243 | var erelx = 0.5-0.5/zoom,
244 | erely = 0.5-0.5/zoom;
245 | var res = tst.imgViewer("imgToView", erelx, erely);
246 | ok(res.x === 0 && res.y === 0, "top left corner at " + zoom + "x zoom is " + res.x + " " + res.y + " expected 0 0");
247 | erelx = 0.5+0.5/zoom;
248 | erely = 0.5+0.5/zoom;
249 | res = tst.imgViewer("imgToView", erelx, erely);
250 | ok(res.x === width && res.y === height, "bottom right corner at " + zoom + "x zoom is " + res.x + " " + res.y + " expected " + width + " " + height);
251 | erelx = 0.5-0.25/zoom;
252 | erely = 0.5-0.25/zoom;
253 | res = tst.imgViewer("imgToView", erelx, erely);
254 | ok(res.x === Math.round(width/4) && res.y === Math.round(height/4), "internal point at " + zoom + "x zoom is " + res.x + " " + res.y + " expected " + Math.round(width/4) + " " + Math.round(height/4));
255 | tst.remove();
256 | start();
257 | }).each(function() {
258 | if (this.complete) { $(this).load(); }
259 | });
260 | });
261 |
262 | asyncTest( "imgToView at 1x zoom with padding and border",1, function() {
263 | var $img = $("#qunit-fixture img");
264 | $img.css({border: "30px solid #ccc", padding: "20px"});
265 | var tst = $img.imgViewer();
266 | $img.load(function() {
267 | var width = $img.width();
268 | var height = $img.height();
269 | var res = tst.imgViewer("imgToView", 0.25, 0.25);
270 | ok(res.x === Math.round(width/4) && res.y === Math.round(height/4), "internal point at 1x zoom is " + res.x + " " + res.y + " expected " + Math.round(width/4) + " " + Math.round(height/4));
271 | tst.remove();
272 | start();
273 | }).each(function() {
274 | if (this.complete) { $(this).load(); }
275 | });
276 | });
277 |
278 | asyncTest( "imgToView at 4x zoom with padding and border",1, function() {
279 | var $img = $("#qunit-fixture img");
280 | var zoom = 4;
281 | $img.css({border: "30px solid #ccc", padding: "20px"});
282 | var tst = $img.imgViewer({zoom: zoom});
283 | $img.load(function() {
284 | var width = $img.width();
285 | var height = $img.height();
286 | var erelx = 0.5-0.25/zoom;
287 | var erely = 0.5-0.25/zoom;
288 | var res = tst.imgViewer("imgToView", erelx, erely);
289 | ok(res.x === Math.round(width/4) && res.y === Math.round(height/4), "internal point at " + zoom + "x zoom is " + res.x + " " + res.y + " expected " + Math.round(width/4) + " " + Math.round(height/4));
290 | tst.remove();
291 | start();
292 | }).each(function() {
293 | if (this.complete) { $(this).load(); }
294 | });
295 | });
296 |
297 | asyncTest( "isVisible",9, function() {
298 | var $img = $("#qunit-fixture img");
299 | var tst = $img.imgViewer();
300 | $img.load(function() {
301 | ok(!(tst.imgViewer("isVisible", -0.1, 0.5)), "at 1x zoom returns false for relative x image coordinate < 0");
302 | ok(!(tst.imgViewer("isVisible", 1.1, 0.5)), "at 1x zoom returns false for relative x image coordinate > 1");
303 | ok(!(tst.imgViewer("isVisible", 0.5, -0.1)), "at 1x zoom returns false for relative y image coordinate < 0");
304 | ok(!(tst.imgViewer("isVisible", 0.5, 1.5)), "at 1x zoom returns false for relative y image coordinate > 1");
305 | ok(tst.imgViewer("isVisible", 0.0, 0.0), "at 1x zoom returns true for top left corner of view");
306 | ok(tst.imgViewer("isVisible", 1.0, 1.0), "at 1x zoom returns true for bottom right corner of view");
307 | ok(tst.imgViewer("isVisible", 0.5, 0.5), "at 1x zoom returns true for point inside view");
308 | var zoom = 4;
309 | tst.imgViewer("option", "zoom", zoom);
310 | ok(!(tst.imgViewer("isVisible", 0.0, 0.0)), "at 4x zoom returns false for point outside view");
311 | ok(tst.imgViewer("isVisible", 0.5, 0.5), "at 4x zoom returns true for point inside view");
312 | tst.remove();
313 | start();
314 | }).each(function() {
315 | if (this.complete) { $(this).load(); }
316 | });
317 | });
318 |
319 | asyncTest( "panTo at 4x zoom",7, function() {
320 | var $img = $("#qunit-fixture img");
321 | var zoom = 4;
322 | var tst = $img.imgViewer({zoom: zoom});
323 | $img.load(function() {
324 | var erelx = 0.5-0.5/zoom,
325 | erely = 0.5-0.5/zoom;
326 | var res = tst.imgViewer("panTo", erelx, erely);
327 | ok(res.x === erelx && res.y === erely, "pan to internal location " + zoom + "x zoom is " + res.x + " " + res.y + " expected " + erelx + " " + erely);
328 | erelx = 1/(2*zoom);
329 | erely = 1/(2*zoom);
330 | res = tst.imgViewer("panTo", 0.0, 0.0);
331 | ok(res.x === erelx && res.y === erely, "pan to top left corner " + zoom + "x zoom is " + res.x + " " + res.y + " expected " + erelx + " " + erely);
332 | erelx = 1 - 1/(2*zoom);
333 | erely = 1 - 1/(2*zoom);
334 | res = tst.imgViewer("panTo", 1.0, 1.0);
335 | ok(res.x === erelx && res.y === erely, "pan to bottom right corner " + zoom + "x zoom is " + res.x + " " + res.y + " expected " + erelx + " " + erely);
336 | equal(tst.imgViewer("panTo", -1.0, 0.5), null, "returns null for relative x image coordinate < 0");
337 | equal(tst.imgViewer("panTo", 2.0, 0.5), null, "returns null for relative x image coordinate > 1");
338 | equal(tst.imgViewer("panTo", 0.5, -0.5), null, "returns null for relative y image coordinate < 0");
339 | equal(tst.imgViewer("panTo", 0.5, 2.5), null, "returns null for relative x image coordinate > 1");
340 | tst.remove();
341 |
342 | start();
343 | }).each(function() {
344 | if (this.complete) { $(this).load(); }
345 | });
346 | });
347 |
348 | asyncTest( "viewToImg at 1x zoom",7, function() {
349 | var $img = $("#qunit-fixture img");
350 | var tst = $img.imgViewer();
351 | $img.load(function() {
352 | var width = $img.width();
353 | var height = $img.height();
354 | var res = tst.imgViewer("viewToImg", 0, 0);
355 | ok(res.x === 0 && res.y === 0, "top left corner at 1x zoom is " + res.x + " " + res.y + " expected 0 0");
356 | res = tst.imgViewer("viewToImg", width, height);
357 | ok(res.x === 1 && res.y === 1, "bottom right corner at 1x zoom is " + res.x + " " + res.y + " expected 1 1");
358 | res = tst.imgViewer("viewToImg", Math.round(width/4), Math.round(height/4));
359 | ok(res.x === 0.25 && res.y === 0.25, "internal point at 1x zoom is " + res.x + " " + res.y + " expected 0.25 0.25");
360 | equal(tst.imgViewer("viewToImg", -1, 0), null, "returns null for view x left of image");
361 | equal(tst.imgViewer("viewToImg", 0, 2*width), null, "returns null for view x right of image > 1");
362 | equal(tst.imgViewer("viewToImg", 0, -1), null, "returns null for view y above image");
363 | equal(tst.imgViewer("viewToImg", 0, 2*height), null, "returns null for view y above image");
364 | tst.remove();
365 | start();
366 | }).each(function() {
367 | if (this.complete) { $(this).load(); }
368 | });
369 | });
370 |
371 | asyncTest( "viewToImg at 4x zoom",3, function() {
372 | var $img = $("#qunit-fixture img");
373 | var zoom = 4;
374 | var tst = $img.imgViewer({zoom: zoom});
375 | $img.load(function() {
376 | var width = $img.width();
377 | var height = $img.height();
378 | var erelx = 0.5-0.5/zoom,
379 | erely = 0.5-0.5/zoom;
380 | var res = tst.imgViewer("viewToImg", 0, 0);
381 | ok(res.x === erelx && res.y === erely, "top left corner at 1x zoom is " + res.x + " " + res.y + " expected " + erelx + " " + erely);
382 | erelx = 0.5+0.5/zoom;
383 | erely = 0.5+0.5/zoom;
384 | res = tst.imgViewer("viewToImg", width, height);
385 | ok(res.x === erelx && res.y === erely, "bottom right corner at 1x zoom is " + res.x + " " + res.y + " expected " + erelx + " " + erely);
386 | erelx = 0.5-0.25/zoom;
387 | erely = 0.5-0.25/zoom;
388 | res = tst.imgViewer("viewToImg", Math.round(width/4), Math.round(height/4));
389 | ok(res.x === erelx && res.y === erely, "internal point at 1x zoom is " + res.x + " " + res.y + " expected " + erelx + " " + erely);
390 | tst.remove();
391 | start();
392 | }).each(function() {
393 | if (this.complete) { $(this).load(); }
394 | });
395 | });
396 |
397 | asyncTest( "viewToImg at 1x zoom with padding and border",1, function() {
398 | var $img = $("#qunit-fixture img");
399 | $img.css({border: "30px solid #ccc", padding: "20px"});
400 | var tst = $img.imgViewer();
401 | $img.load(function() {
402 | var width = $img.width();
403 | var height = $img.height();
404 | var res = tst.imgViewer("viewToImg", Math.round(width/4), Math.round(height/4));
405 | ok(res.x === 0.25 && res.y === 0.25, "internal point at 1x zoom is " + res.x + " " + res.y + " expected 0.25 0.25");
406 | tst.remove();
407 | start();
408 | }).each(function() {
409 | if (this.complete) { $(this).load(); }
410 | });
411 | });
412 |
413 | asyncTest( "viewToImg at 4x zoom with padding and border",1, function() {
414 | var $img = $("#qunit-fixture img");
415 | var zoom = 4;
416 | $img.css({border: "30px solid #ccc", padding: "20px"});
417 | var tst = $img.imgViewer({zoom: zoom});
418 | $img.load(function() {
419 | var width = $img.width();
420 | var height = $img.height();
421 | var erelx = 0.5-0.25/zoom;
422 | var erely = 0.5-0.25/zoom;
423 | var res = tst.imgViewer("viewToImg", Math.round(width/4), Math.round(height/4));
424 | ok(res.x === erelx && res.y === erely, "internal point at 1x zoom is " + res.x + " " + res.y + " expected " + erelx + " " + erely);
425 | tst.remove();
426 | start();
427 | }).each(function() {
428 | if (this.complete) { $(this).load(); }
429 | });
430 | });
431 |
432 | }(jQuery));
433 |
434 |
435 |
--------------------------------------------------------------------------------