diff --git a/MIT-LICENSE.txt b/LICENSE.md similarity index 86% rename from MIT-LICENSE.txt rename to LICENSE.md index 7f1e8a2..e686bdf 100644 --- a/MIT-LICENSE.txt +++ b/LICENSE.md @@ -1,3 +1,7 @@ +jQuery v1 is released under the MIT license described below. jQuery v2 is +under development and may be released under a different licensing model, +including a commercial option. + Copyright 2014 Jonathon Menz and other contributors, https://github.com/jonom/jquery-focuspoint diff --git a/README.md b/README.md index 5879064..82dce4d 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,16 @@ -# jQuery FocusPoint +# jQuery FocusPoint 2 -## FocusPoint 2 +**Note:** This branch is currently under development and and is not intended for production use. The licensing model may change for version 2 and a commercial license may be required for use in commercial projects. This is intended to ensure I can continue to support this project in a sustainable fashion and is likely to be inexpensive. Contributions are welcome as long as you are comfortable with this potential change. -I started work on a major update to this plugin a couple of years ago, but it has been in limbo for a while and probably won't be picked up again unless someone wants to sponsor the work (please [get in touch](http://jonathonmenz.com) if you do!). I didn't get as far as removing the jQuery dependancy but please feel free to use the v-2-dev branch to benefit from a few new features: +## Art direction for flexible image containers -* Better resizing performance -* More familiar coordinate system (like that used in CSS) -* Suppor for ideal and minimum cropping region - -If you love this plugin feel free to send me an encouraging email or consider sponsoring an update. You're also welcome to make a [small donation](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=5VUDD3ACRC4TC) if you like. I receive an average of one per year, so it won't go unnoticed. 😄💰 - -## Intelligent cropping for flexible image containers - -![image](demos/img/demo.jpg?raw=true) +![Cropping comparison](demos/img/demo.jpg) Websites don't have a single layout any more. The space you have for an image may be portrait on a laptop, landscape on a tablet, and square on a mobile - particularly if you're using a full-screen image. If you have to use the same image file in all these contexts, you might not be happy with the results you get when you 'fill' the allocated space with your image. Your subject might be clipped or completely missing, or just really awkward looking. -FocusPoint makes sure your image looks great in any container, by ensuring the 'spare' parts of your image (negative space) are cropped out before the important parts. +FocusPoint makes sure your image looks great in any container, by ensuring the most important part of your image always stays within the frame. For a quick overview of the plugin check out this [video by Petr Tichy](http://youtu.be/Wxmxsw65BQw?t=6m49s). @@ -26,29 +18,36 @@ For a quick overview of the plugin check out this [video by Petr Tichy](http://y Here are some examples showing the same image cropped a variety of different ways at once. Make sure you play with resizing the browser window to get a feel for what FocusPoint does. -* [Lizard](http://jonom.github.io/jquery-focuspoint/demos/grid/lizard.html) -* [Kangaroo](http://jonom.github.io/jquery-focuspoint/demos/grid/kangaroo.html) -* [Dolphin](http://jonom.github.io/jquery-focuspoint/demos/grid/dolphin.html) -* [Bird](http://jonom.github.io/jquery-focuspoint/demos/grid/bird.html) +* [Lizard](http://jonom.github.io/jquery-focuspoint/demos/grid/lizard.html) +* [Kangaroo](http://jonom.github.io/jquery-focuspoint/demos/grid/kangaroo.html) +* [Dolphin](http://jonom.github.io/jquery-focuspoint/demos/grid/dolphin.html) +* [Bird](http://jonom.github.io/jquery-focuspoint/demos/grid/bird.html) And here is a [full screen](http://jonom.github.io/jquery-focuspoint/demos/full-screen/index.html) demo. +## In the wild + +FocusPoint helps with art direction on [Adele's website](http://adele.com/home/). + +Are you using FocusPoint on a cool or high profile website? [Let me know!](http://jonathonmenz.com) + ## How does it work? The idea is that most images have a focal point or subject that is the most important part of the image. In the case of a traditional portrait photo this would be the subject's face (or specifically the spot right between their eyes). In the image above it's arguably the point halfway between the two people's faces. FocusPoint requires you to indicate where this focal point is located within your image, and then works in the background to ensure that point is never cropped out. - ## How to use #### 1. Calculate your image's focus point -An image's focus point is made up of x (horizontal) and y (vertical) coordinates. The value of a coordinate can be a number with decimal points anywhere between -1 and +1, where 0 is the centre. X:-1 indicates the left edge of the image, x:1 the right edge. For the y axis, y:1 is the top edge and y:-1 is the bottom. +An image's focus point is made up of `x` (horizontal) and `y` (vertical) coordinates. The value of a coordinate can be a decimal point number anywhere between `0` and `1` and works similar to positioning in CSS. On the `x` axis `0` means the left edge and `1` means the right, while on the `y` axis `0` means the top and `1` means the bottom. On both axes `0.5` indicates the center. -![image](demos/img/grid.png?raw=true) +![Grid diagram](demos/img/grid.png) -**Confused?** Don't worry, there's a handy script included to help you find the focus coordinates of an image with a single click. Check out the [helper tool](http://jonom.github.io/jquery-focuspoint/demos/helper/index.html) *(vastly improved courtesy of [@auginator](https://github.com/auginator)).* +*Note: Version 1 of this plugin used a different grid and coordinate system. If you like you can keep using the old system by using the `legacyGrid` setting (see below).* + +**Sound hard?** Don't worry, you can easily find the focus point coordinates of any image by using the [helper tool](http://jonom.github.io/jquery-focuspoint/demos/helper/index.html) *(courtesy of [@auginator](https://github.com/auginator)).* #### 2. Include javascript and CSS @@ -67,7 +66,7 @@ Specify the image dimensions and focus point coordinates on the image container. ```html
@@ -93,14 +92,15 @@ FocusPoint comes with a few options you can change to suit your needs. | Option | Values | Default | Description | | ---------------------- | --------------------- | ------- | ----------- | -| `reCalcOnWindowResize` | `true` or `false` | `true` | Whether or not to re-adjust image when the window is resized | -| `throttleDuration` | Int e.g. `0` or `100` | `17` | Throttling rate in milliseconds. Set to `0` to disable throttling. | +| `reCalcOnWindowResize` | `true` or `false` | `true` | Whether or not to re-adjust image when the window is resized. | +| `setTransformOrigin` | `true` or `false` | `true` | Whether or not to set the images transform origin to match the focus point. This allows for easy zooming from the focus point with css transforms. | +| `legacyGrid` | `true` or `false` | `false` | Set to true to use FocusPoint v1 style coordinates and grid system. | Example usage: ```javascript $('.focuspoint').focusPoint({ - throttleDuration: 100 //re-focus images at most once every 100ms. + reCalcOnWindowResize: false // Don't automatically follow window resize event }); ``` @@ -112,10 +112,20 @@ Or the shorter way, like this: `$(someContainer).focusPoint('methodName')` | Function | Description | | --------------- | ----------- | -| `adjustFocus()` | Re-do calculations and re-position an image in it's frame. Call if container dimensions change. | -| `windowOn()` | Start window event listener and re-focus image when window is resized | +| `adjustFocus()` | Re-do calculations and re-position an image in it's frame. Should be called when container dimensions change. | +| `windowOn()` | Start window event listener to automatically re-focus this image when window is resized | | `windowOff()` | Stop re-focusing image when window is resized | +## Advanced usage + +#### Minimum cropping region + +Suppose you have an image that looks okay in a square or portrait frame, but it really doesn't work in a landscape container, due to lack of vertical negative space in the image. In that case you might want the image to fill the frame for square or portrait aspect ratios, but only partially fill the frame in landscape ones. You can do this by specifying a minimum cropping region. All you need to do is pass two comma separated `x` coordinates and/or two `y` coordinates instead of one. + +![diagram of cropping region](demos/img/minimum-region.png) + +[See an example](http://jonom.github.io/jquery-focuspoint/demos/test/index.html) + #### Using FocusPoint in content sliders Currently FocusPoint can't do it's calculations properly if an image container or it's parent is set to `display:none`, as it won't have any dimensions. This can cause problems with sliders that hide non-active slides. A work-around for now is to trigger `adjustFocus()` on the image container as soon as it become visible. @@ -148,24 +158,6 @@ If FocusPoint helped you impress a client and you want to say thanks, you're wel [Donate](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=5VUDD3ACRC4TC) -Donations received / Warm fuzzies generated: **6** -Caffé Lattes funded: **17** :coffee: :relieved: +Donations received / Warm fuzzies generated: **6** +Caffé Lattes funded: **17** :coffee: :relieved: **Thanks!** Daniil, Cohan, Romulo, Lemuel, David - -## Changelog - -#### v1.1.1 2014-09-23 -Minor fixes -#### v1.1.0 2014-09-18 -Refactored code (thanks @xat) -Added ability to start/stop window-resize listener (thanks @xat) -Use % instead of px for positioning, for better scaling -Added shortcuts to plugin methods -#### v1.0.3 2014-09-06 -Throttled window resize updates -#### v1.0.2 2014-09-05 -Made setting image width and height on shell optional (thanks @luruke) -#### v1.0.1 2014-09-04 -Cleaned up variables -#### v1.0.0 2014-08-19 -Initial release diff --git a/css/focuspoint.css b/css/focuspoint.css index 7305b50..f4d9bc1 100755 --- a/css/focuspoint.css +++ b/css/focuspoint.css @@ -4,14 +4,20 @@ position: relative; /*Any position but static should work*/ overflow: hidden; } -.focuspoint img { +.focuspoint img, +.focuspoint video { position: absolute; left: 0; top: 0; margin: 0; display: block; - /* fill and maintain aspect ratio */ + /* fill and maintain aspect ratio without JS */ width: auto; height: auto; min-width: 100%; min-height: 100%; max-height: none; max-width: none; +} +.focuspoint-active img, +.focuspoint-active video { + min-width: 0; + min-height: 0; } \ No newline at end of file diff --git a/demos/css-js-comparison/index.html b/demos/css-js-comparison/index.html index bb53ef0..9005b8c 100644 --- a/demos/css-js-comparison/index.html +++ b/demos/css-js-comparison/index.html @@ -35,8 +35,8 @@
diff --git a/demos/full-screen/index.html b/demos/full-screen/index.html index 09fc000..051bb23 100644 --- a/demos/full-screen/index.html +++ b/demos/full-screen/index.html @@ -33,8 +33,8 @@
diff --git a/demos/grid/bird.html b/demos/grid/bird.html index 63990bf..5fc7b27 100644 --- a/demos/grid/bird.html +++ b/demos/grid/bird.html @@ -35,72 +35,72 @@
diff --git a/demos/grid/dolphin.html b/demos/grid/dolphin.html index cdeb36b..a5b97c1 100644 --- a/demos/grid/dolphin.html +++ b/demos/grid/dolphin.html @@ -41,72 +41,72 @@
diff --git a/demos/grid/grid.css b/demos/grid/grid.css index f451a26..7baef30 100755 --- a/demos/grid/grid.css +++ b/demos/grid/grid.css @@ -22,7 +22,9 @@ body { position: absolute; overflow: hidden; border: 5px solid #fff; - margin: -5px 0 0 -5px; + background: #ccc; + box-shadow: inset 0px 0px 65px rgba(0, 0, 0, 0.4); + box-sizing: border-box; } #Frame1, #Frame4, #Frame7 { width: 66.6666667%; diff --git a/demos/grid/ideal-cropping-region.html b/demos/grid/ideal-cropping-region.html new file mode 100644 index 0000000..c15c009 --- /dev/null +++ b/demos/grid/ideal-cropping-region.html @@ -0,0 +1,126 @@ + + + + + + + FocusPoint Example + + + + + + + + + + + + + + +
+ +
+ +
+ +
+ +
+ + + +
+ +
+
+ +

Original

+
+

Try resizing the window. The image will fill each frame as much as it can, until that would prevent all of the minimum cropping region being shown. » Project Home

+
+ + + diff --git a/demos/grid/kangaroo.html b/demos/grid/kangaroo.html index d615ef4..aaeb383 100644 --- a/demos/grid/kangaroo.html +++ b/demos/grid/kangaroo.html @@ -41,72 +41,72 @@
diff --git a/demos/grid/lizard.html b/demos/grid/lizard.html index 2bd02cd..612d4b0 100644 --- a/demos/grid/lizard.html +++ b/demos/grid/lizard.html @@ -35,72 +35,72 @@
diff --git a/demos/grid/min-cropping-region.html b/demos/grid/min-cropping-region.html new file mode 100644 index 0000000..de357a4 --- /dev/null +++ b/demos/grid/min-cropping-region.html @@ -0,0 +1,120 @@ + + + + + + + FocusPoint Example + + + + + + + + + + + + + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +

Original

+
+

Try resizing the window. The image will fill each frame as much as it can, until that would prevent all of the minimum cropping region being shown. » Project Home

+
+ + + diff --git a/demos/img/bird-min.jpg b/demos/img/bird-min.jpg new file mode 100644 index 0000000..a3c5354 Binary files /dev/null and b/demos/img/bird-min.jpg differ diff --git a/demos/img/bird.jpg b/demos/img/bird.jpg index cebf554..3afaac0 100644 Binary files a/demos/img/bird.jpg and b/demos/img/bird.jpg differ diff --git a/demos/img/city_from_unsplash.jpg b/demos/img/city_from_unsplash.jpg index f16dc6d..e85df29 100644 Binary files a/demos/img/city_from_unsplash.jpg and b/demos/img/city_from_unsplash.jpg differ diff --git a/demos/img/cloudy-water.mp4 b/demos/img/cloudy-water.mp4 new file mode 100644 index 0000000..a4f352b Binary files /dev/null and b/demos/img/cloudy-water.mp4 differ diff --git a/demos/img/demo.jpg b/demos/img/demo.jpg index 870acdf..8a7dbcf 100644 Binary files a/demos/img/demo.jpg and b/demos/img/demo.jpg differ diff --git a/demos/img/dolphin.jpg b/demos/img/dolphin.jpg index c6b5c4a..1c9582f 100644 Binary files a/demos/img/dolphin.jpg and b/demos/img/dolphin.jpg differ diff --git a/demos/img/focuspoint-target.png b/demos/img/focuspoint-target.png index 998994f..f880138 100644 Binary files a/demos/img/focuspoint-target.png and b/demos/img/focuspoint-target.png differ diff --git a/demos/img/grid.png b/demos/img/grid.png index 0b4ee9e..36c655c 100644 Binary files a/demos/img/grid.png and b/demos/img/grid.png differ diff --git a/demos/img/kangaroo.jpg b/demos/img/kangaroo.jpg index a44d226..5dd6c3d 100644 Binary files a/demos/img/kangaroo.jpg and b/demos/img/kangaroo.jpg differ diff --git a/demos/img/lizard-min.jpg b/demos/img/lizard-min.jpg new file mode 100644 index 0000000..84b1427 Binary files /dev/null and b/demos/img/lizard-min.jpg differ diff --git a/demos/img/lizard.jpg b/demos/img/lizard.jpg index fc96985..c1cd2bf 100644 Binary files a/demos/img/lizard.jpg and b/demos/img/lizard.jpg differ diff --git a/demos/img/minimum-region.png b/demos/img/minimum-region.png new file mode 100644 index 0000000..19f0622 Binary files /dev/null and b/demos/img/minimum-region.png differ diff --git a/demos/test/index.html b/demos/test/index.html index dcfd717..b8ff841 100644 --- a/demos/test/index.html +++ b/demos/test/index.html @@ -21,31 +21,30 @@ $(document).ready(function() { //Activate test cases individually - $('#Default').focusPoint(); - $('#MissingWH').focusPoint(); - $('#ThrottleSlow').focusPoint({ - throttleDuration: 500 //ms - }); - $('#NoThrottle').focusPoint({ - throttleDuration: 0 //ms - }); - $('#NoResize').focusPoint({ + $('#Default .focuspoint, #MissingWH .focuspoint, #Zoom .focuspoint, #MinRegion .focuspoint, #Video .focuspoint').focusPoint(); + $('#LegacyGrid .focuspoint').focusPoint({legacyGrid: true}); + $('#Zoom a').click(function(e){ + e.preventDefault(); + var scaleCSS = 'scale(' + $(this).data('scale') + ')'; + $('#Zoom img').css({ + 'ms-transform': scaleCSS, + 'webkit-transform': scaleCSS, + 'transform': scaleCSS + }); + }).get(2).click(); + $('#NoResize .focuspoint').focusPoint({ reCalcOnWindowResize : false }); $('#NoResize a.adjust').click(function(){ - $('#NoResize').focusPoint('adjustFocus'); //Short for .data('focusPoint').adjustFocus(); + $('#NoResize .focuspoint').focusPoint('adjustFocus'); //Short for .data('focusPoint').adjustFocus(); return false; }); - $('#NoResize a.adjust2').click(function(){ - $('#NoResize').adjustFocus(); //Old method + $('#NoResize a.resize-on').click(function(){ + $('#NoResize .focuspoint').focusPoint('windowOn'); return false; }); - $('#NoResize a.start').click(function(){ - $('#NoResize').focusPoint('windowOn'); - return false; - }); - $('#NoResize a.stop').click(function(){ - $('#NoResize').focusPoint('windowOff'); + $('#NoResize a.resize-off').click(function(){ + $('#NoResize .focuspoint').focusPoint('windowOff'); return false; }); @@ -60,53 +59,130 @@

FocusPoint Tests » Project Home

-
- +
+
+ +
+
+ +
Default behaviour
-
- - Missing image-w and image-h +
+
+ +
+
+ +
+ Legacy grid
-
- - Slow redraw (high throttleDuration) +
+
+ +
+
+ +
+ Minimum cropping region
-
- - No throttling +
+
+ +
+
+ +
+ Missing image-w and image-h +
+ +
+
+ +
+
+ +
+ Scale: + 1x + 1.5x + 3x + 5x
-
- + +
+
+ +
+ Video +
+ diff --git a/demos/test/test.css b/demos/test/test.css index a7630f7..ffffdbb 100755 --- a/demos/test/test.css +++ b/demos/test/test.css @@ -1,11 +1,28 @@ /* !CONTAINERS */ /*-----------------------------------------*/ +.test { + margin-bottom: 20px; + position: relative; + background: #ccc; +} +.test:after { + content: ""; + display: table; + clear: both; +} .focuspoint { + float: left; + width: 50%; height: 200px; - margin-bottom: 20px; } -.focuspoint .label { +/* +img, +video { + transition: transform .5s, width .5s, height .5s, left .5s, top .5s; +} +*/ +.test .label { position: absolute; left: 0; bottom: 0; diff --git a/js/jquery.focuspoint.helper-basic.js b/js/jquery.focuspoint.helper-basic.js index 961b5d6..9be3f02 100755 --- a/js/jquery.focuspoint.helper-basic.js +++ b/js/jquery.focuspoint.helper-basic.js @@ -11,8 +11,8 @@ //Calculate FocusPoint coordinates var offsetX = e.pageX - $(this).offset().left; var offsetY = e.pageY - $(this).offset().top; - var focusX = (offsetX/imageW - .5)*2; - var focusY = (offsetY/imageH - .5)*-2; + var focusX = offsetX/imageW; + var focusY = offsetY/imageH; //Calculate CSS Percentages var percentageX = (offsetX/imageW)*100; diff --git a/js/jquery.focuspoint.helpertool.js b/js/jquery.focuspoint.helpertool.js index d406894..ee10ff0 100644 --- a/js/jquery.focuspoint.helpertool.js +++ b/js/jquery.focuspoint.helpertool.js @@ -119,8 +119,8 @@ //Calculate FocusPoint coordinates var offsetX = e.pageX - $(this).offset().left; var offsetY = e.pageY - $(this).offset().top; - var focusX = (offsetX/imageW - .5)*2; - var focusY = (offsetY/imageH - .5)*-2; + var focusX = offsetX/imageW; + var focusY = offsetY/imageH; focusPointAttr.x = focusX; focusPointAttr.y = focusY; @@ -168,19 +168,12 @@ function updateFocusPoint(){ /*-----------------------------------------*/ - // See note in setImage() function regarding these attribute assignments. - //TLDR - You don't need them for this to work. - /*-----------------------------------------*/ - $focusPointContainers.attr({ - 'data-focus-x': focusPointAttr.x, - 'data-focus-y': focusPointAttr.y - }); - /*-----------------------------------------*/ - // These you DO need :) + // Re-initialise each container as focuspoint may have changed /*-----------------------------------------*/ $focusPointContainers.data('focusX', focusPointAttr.x); $focusPointContainers.data('focusY', focusPointAttr.y); - $focusPointContainers.adjustFocus(); + $focusPointContainers.focusPoint('init'); + $focusPointContainers.focusPoint('adjustFocus'); }; }); }(jQuery)); \ No newline at end of file diff --git a/js/jquery.focuspoint.js b/js/jquery.focuspoint.js index a720d74..576f5d9 100755 --- a/js/jquery.focuspoint.js +++ b/js/jquery.focuspoint.js @@ -1,191 +1,339 @@ /** - * jQuery FocusPoint; version: 1.1.3 - * Author: http://jonathonmenz.com + * jQuery FocusPoint; version: v2-dev + * Author: J. Menz http://jonathonmenz.com * Source: https://github.com/jonom/jquery-focuspoint - * Copyright (c) 2014 J. Menz; MIT License + * Copyright (c) 2014 - 2016 J. Menz; License TBC * @preserve */ -; -(function($) { +;(function ( $, window, document, undefined ) { var defaults = { reCalcOnWindowResize: true, - throttleDuration: 17 //ms - set to 0 to disable throttling + setTransformOrigin: true, + legacyGrid: false }; + var $resizeElements = $(); // Which focuspoint containers are listening to resize event - //Setup a container instance - var setupContainer = function($el) { - var imageSrc = $el.find('img').attr('src'); - $el.data('imageSrc', imageSrc); - - resolveImageSize(imageSrc, function(err, dim) { - $el.data({ - imageW: dim.width, - imageH: dim.height - }); - adjustFocus($el); - }); - }; - - //Get the width and the height of an image - //by creating a new temporary image - var resolveImageSize = function(src, cb) { - //Create a new image and set a - //handler which listens to the first - //call of the 'load' event. - $('').one('load', function() { - //'this' references to the new - //created image - cb(null, { - width: this.width, - height: this.height - }); - }).attr('src', src); - }; - - //Create a throttled version of a function - var throttle = function(fn, ms) { - var isRunning = false; + // https://davidwalsh.name/javascript-debounce-function + var debounce = function(func, wait, immediate) { + var timeout; return function() { - var args = Array.prototype.slice.call(arguments, 0); - if (isRunning) return false; - isRunning = true; - setTimeout(function() { - isRunning = false; - fn.apply(null, args); - }, ms); + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); }; }; - //Calculate the new left/top values of an image - var calcShift = function(conToImageRatio, containerSize, imageSize, focusSize, toMinus) { - var containerCenter = Math.floor(containerSize / 2); //Container center in px - var focusFactor = (focusSize + 1) / 2; //Focus point of resize image in px - var scaledImage = Math.floor(imageSize / conToImageRatio); //Can't use width() as images may be display:none - var focus = Math.floor(focusFactor * scaledImage); - if (toMinus) focus = scaledImage - focus; - var focusOffset = focus - containerCenter; //Calculate difference between focus point and center - var remainder = scaledImage - focus; //Reduce offset if necessary so image remains filled - var containerRemainder = containerSize - containerCenter; - if (remainder < containerRemainder) focusOffset -= containerRemainder - remainder; - if (focusOffset < 0) focusOffset = 0; - - return (focusOffset * -100 / containerSize) + '%'; + // Resize throttling. Let requestAnimationFrame set framerate rather than using resize event rate which may be too rapid or too slow. + var running = false; + // Called on resize event + var resize = function() { + if (!running) { + running = true; + doResize(); + cancelResize(); + } }; - - //Re-adjust the focus - var adjustFocus = function($el) { - var imageW = $el.data('imageW'); - var imageH = $el.data('imageH'); - var imageSrc = $el.data('imageSrc'); - - if (!imageW && !imageH && !imageSrc) { - return setupContainer($el); //Setup the container first + var cancelResize = debounce(function() { + running = false; + }, 250); + var doResize = function() { + $resizeElements.focusPoint('adjustFocus'); + // Repeat if still running + if (running) { + if (window.requestAnimationFrame) { + window.requestAnimationFrame(doResize); + } else { + setTimeout(doResize, 1000/15); //15fps fallback + } } + }; - var containerW = $el.width(); - var containerH = $el.height(); - var focusX = parseFloat($el.data('focusX')); - var focusY = parseFloat($el.data('focusY')); - var $image = $el.find('img').first(); + // Single resize listener for all focus point instances + var updateResizeListener = function() { + $(window).off('resize.focuspoint'); + if ($resizeElements.length) { + $(window).on('resize.focuspoint', resize); + } + }; - //Amount position will be shifted - var hShift = 0; - var vShift = 0; + // For sorting + var compareNumbers = function(a, b) { + return a - b; + }; - if (!(containerW > 0 && containerH > 0 && imageW > 0 && imageH > 0)) { - return false; //Need dimensions to proceed + // Calculate the left or top offset for an image on an axis + var getOffset = function(focusPos, scale) { + // How much bigger is the image than the container? + var spareSpace = scale - 1; + // If the image doesn't fill the container, position image on center + if (spareSpace < 0) { + return (1 - scale) * 0.5; } + // Otherwise move image so focus point is as close to center of frame as possible + else if (spareSpace > 0) { + var negSpareSpace = spareSpace * -1; + var offset = 0.5 - (focusPos * scale); + if (offset > 0) { + return 0; + } + if (offset < 0 && offset < negSpareSpace) { + return negSpareSpace; + } + return offset; + } + return 0; + }; - //Which is over by more? - var wR = imageW / containerW; - var hR = imageH / containerH; + // Convert one or two coords in to an array of min,max and average values. If one coord is passed they will all be the same. + var getRange = function(axis, coordString, legacyGrid) { + var axisCoords = coordString.split(','), + axisRange = []; - //Reset max-width and -height - $image.css({ - 'max-width': '', - 'max-height': '' + // Make numeric and account for classic grid + $.each(axisCoords, function( index, value ) { + axisCoords[index] = parseFloat(value.trim()); + if (legacyGrid === true) { + if (axis === 'X') { + axisCoords[index] = (axisCoords[index] + 1)*0.5; + } + else { + axisCoords[index] = (axisCoords[index] - 1)*-0.5; + } + } }); - //Minimize image while still filling space - if (imageW > containerW && imageH > containerH) { - $image.css((wR > hR) ? 'max-height' : 'max-width', '100%'); + // Set ranges + if (axisCoords.length > 1) { + axisCoords.sort(compareNumbers); + axisRange.start = axisCoords[0]; + axisRange.stop = axisCoords[1]; + // Average of min cropping region for classic focus point + axisRange.average = (axisCoords[0] + axisCoords[1]) / 2; } - - if (wR > hR) { - hShift = calcShift(hR, containerW, imageW, focusX); - } else if (wR < hR) { - vShift = calcShift(wR, containerH, imageH, focusY, true); + else { + // Same values for all + axisRange.start = axisRange.stop = axisRange.average = axisCoords[0]; } + // Ratio of range to image on this axis, inverted so we can determine the maximum allowable zoom level before the region will not all be viewable + axisRange.maxScale = 1 / (axisRange.stop - axisRange.start); - $image.css({ - top: vShift, - left: hShift - }); + return axisRange; }; - var $window = $(window); + // The FocusPoint plugin constructor + function FocusPoint ( element, options ) { + this.element = element; + this.settings = $.extend( {}, defaults, options ); + this.init(); + } - var focusPoint = function($el, settings) { - var thrAdjustFocus = settings.throttleDuration ? - throttle(function(){adjustFocus($el);}, settings.throttleDuration) - : function(){adjustFocus($el);};//Only throttle when desired - var isListening = false; + // Avoid Plugin.prototype conflicts + $.extend(FocusPoint.prototype, { + init: function () { + // Set up the values which won't change + this.$el = $(this.element); + this.$image = this.$el.find('img, video').first(); + this.imageW = this.$el.data('imageW'); + this.imageH = this.$el.data('imageH'); + this.imageRatio = this.imageW / this.imageH; - adjustFocus($el); //Focus image in container + // Determine image dimensions if missing + if (!this.imageW && !this.imageH) { + return this.resolveImageSize(); + } - //Expose a public API - return { + // Separate and sanitise focus points. + // There may be one or two focus coordinates per axis, defining a single point or area + var fp = this; + $.each(['X','Y'], function( index, axis ) { + // Get the main coordinates, including min cropping region if set + var axisRange = getRange(axis, String(fp.$el.data('focus' + axis)), fp.settings.legacyGrid); + fp['focus' + axis + 'MinStart'] = axisRange.start; + fp['focus' + axis + 'MinStop'] = axisRange.stop; + fp['focus' + axis] = axisRange.average; + fp['minCroppingRegionMaxScale' + axis] = axisRange.maxScale; - adjustFocus: function() { - return adjustFocus($el); - }, + // Get the ideal region coordinates if set + axisRange = getRange(axis, String(fp.$el.data('focus' + axis + 'Ideal')), fp.settings.legacyGrid); + fp['focus' + axis + 'IdealStart'] = axisRange.start; + fp['focus' + axis + 'IdealStop'] = axisRange.stop; + fp['idealCroppingRegionMaxScale' + axis] = axisRange.maxScale; + }); - windowOn: function() { - if (isListening) return; - //Recalculate each time the window is resized - $window.on('resize', thrAdjustFocus); - return isListening = true; - }, + this.minCroppingRegionW = (this.focusXMinStop - this.focusXMinStart) * this.imageW; + this.minCroppingRegionH = (this.focusYMinStop - this.focusYMinStart) * this.imageH; + this.minCroppingRegionRatio = this.minCroppingRegionW / this.minCroppingRegionH; + this.idealCroppingRegionW = (this.focusXIdealStop - this.focusXIdealStart) * this.imageW; + this.idealCroppingRegionH = (this.focusYIdealStop - this.focusYIdealStart) * this.imageH; + this.idealCroppingRegionRatio = this.idealCroppingRegionW / this.idealCroppingRegionH; - windowOff: function() { - if (!isListening) return; - //Stop listening to the resize event - $window.off('resize', thrAdjustFocus); - isListening = false; - return true; + // Set transform origin + if (this.settings.setTransformOrigin) { + var transformOrigin = (fp.focusX * 100) + '% ' + (fp.focusY * 100) + '%'; + this.$image.css({ + 'webkit-transform-origin': transformOrigin, + 'transform-origin': transformOrigin + }); + } + // Mark active + this.$el.addClass('focuspoint-active'); + // Focus image in container + this.adjustFocus(); + // Adjust on resize + if (this.settings.reCalcOnWindowResize) { + this.windowOn(); } + }, + // Get the width and the height of an image by creating a new temporary image + resolveImageSize: function() { + // Don't try this more than once + if (this.triedAutoResolution) { + return false; + } + this.triedAutoResolution = true; + // Create a new image and set a handler which listens to the first call of the 'load' event. + var $el = this.$el; + $('').one('load', function() { + // 'this' references to the new created image + $el.data('imageW', this.width); + $el.data('imageH', this.height); + $el.focusPoint('init'); + }).attr('src', this.$image.attr('src')); + }, + // Adjust focus automatically when screen is resized + windowOn: function() { + $resizeElements = $resizeElements.add(this.element); + updateResizeListener(); + }, + // Cancel automatic adjustments + windowOff: function() { + $resizeElements = $resizeElements.not(this.element); + updateResizeListener(); + }, + // Optimally position image in container + adjustFocus: function() { + // Store all the cropping data in one var for easy debugging + var data = {}; + data.containerW = this.$el.width(); + data.containerH = this.$el.height(); + if (!(data.containerW > 0 && data.containerH > 0 && this.imageW > 0 && this.imageH > 0)) { + return false; //Need dimensions to proceed + } + data.containerRatio = data.containerW / data.containerH; - }; - }; + // Stop processing if the image and container aspect ratio match + if (data.containerRatio === this.imageRatio) { + this.$image.css({ + 'width': '100%', + 'height': '100%', + 'left': 0, + 'top': 0 + }); + return; + } + + // ToDo: this is weird + data.axisScale = { + X: this.imageRatio / data.containerRatio, + Y: data.containerRatio / this.imageRatio + }; - $.fn.focusPoint = function(optionsOrMethod) { - //Shortcut to functions - if string passed assume method name and execute + // Determing the method of cropping to use + if (data.containerRatio >= this.idealCroppingRegionRatio && data.containerRatio <= (this.imageW / this.idealCroppingRegionH)) { + // Show entire ideal cropping range area, with extra image shown on the X axis + data.clippingMode = 'ideal'; + data.primaryClippingAxis = 'Y'; + data.secondaryClippingAxis = 'X'; + } + else if (data.containerRatio <= this.idealCroppingRegionRatio && data.containerRatio >= (this.idealCroppingRegionW / this.imageH)) { + // Show entire ideal cropping range area, with extra image shown on the Y axis + data.clippingMode = 'ideal'; + data.primaryClippingAxis = 'X'; + data.secondaryClippingAxis = 'Y'; + } + else if (this.imageRatio > data.containerRatio) { + // Show as much of the image as possible, cropping on the X axis + data.clippingMode = 'classic'; + data.primaryClippingAxis = 'X'; + if (data.axisScale.X > this.minCroppingRegionMaxScaleX) { + // Shrink the image to preserve the entire minimum cropping region, padding evenly on the Y axis + data.clippingMode = 'min'; + } + } + else { + // Show as much of the image as possible, cropping on the Y axis + data.clippingMode = 'classic'; + data.primaryClippingAxis = 'Y'; + if (data.axisScale.Y > this.minCroppingRegionMaxScaleY) { + // Shrink the image to preserve the entire minimum cropping region, padding evenly on the X axis + data.clippingMode = 'min'; + } + } + + data.scale = 1; + data.shiftPrimary = 0; + data.shiftSecondary = 0; + if (data.clippingMode === 'ideal') { + // Scale up image to fit ideal cropping region in frame + data.scale = this['idealCroppingRegionMaxScale' + data.primaryClippingAxis] / data.axisScale[data.primaryClippingAxis]; + data.shiftSecondary = (getOffset(this['focus' + data.secondaryClippingAxis], data.scale) * 100) + '%'; + data.shiftPrimary = (this['focus' + data.primaryClippingAxis + 'IdealStart'] * -100 * data.axisScale[data.primaryClippingAxis] * data.scale) + '%'; + } + else if (data.clippingMode === 'min') { + // Scale down image to fit min cropping region in frame + data.scale = this['minCroppingRegionMaxScale' + data.primaryClippingAxis] / data.axisScale[data.primaryClippingAxis]; + data.shiftSecondary = (getOffset(this['focus' + data.secondaryClippingAxis], data.scale) * 100) + '%'; + data.shiftPrimary = (this['focus' + data.primaryClippingAxis + 'MinStart'] * -100 * data.axisScale[data.primaryClippingAxis] * data.scale) + '%'; + } + else { + // Show as much image as possible, with focus point as close to center as possible + data.shiftPrimary = (getOffset(this['focus' + data.primaryClippingAxis], data.axisScale[data.primaryClippingAxis]) * 100) + '%'; + } + + // Assign CSS values to image container + if (data.primaryClippingAxis === 'X') { + this.$image.css({ + 'width': ((data.axisScale[data.primaryClippingAxis] * data.scale * 100) + '%'), + 'height': ((data.scale * 100) + '%'), + 'left': (data.shiftPrimary), + 'top': (data.shiftSecondary) + }); + } + else { //clippingAxis === 'Y' + this.$image.css({ + 'width': ((data.scale * 100) + '%'), + 'height': ((data.axisScale[data.primaryClippingAxis] * data.scale * 100) + '%'), + 'left': (data.shiftSecondary), + 'top': (data.shiftPrimary) + }); + } + } + }); + + // Plugin wrapper around the constructor, preventing against multiple instantiations + $.fn.focusPoint = function ( optionsOrMethod ) { + this.each(function() { + if ( !$.data( this, "focusPoint" ) ) { + $.data( this, "focusPoint", new FocusPoint( this, optionsOrMethod ) ); + } + }); + // Shortcut to functions - if string passed assume method name and execute if (typeof optionsOrMethod === 'string') { return this.each(function() { var $el = $(this); $el.data('focusPoint')[optionsOrMethod](); }); } - //Otherwise assume options being passed and setup - var settings = $.extend({}, defaults, optionsOrMethod); - return this.each(function() { - var $el = $(this); - var fp = focusPoint($el, settings); - //Stop the resize event of any previous attached - //focusPoint instances - if ($el.data('focusPoint')) $el.data('focusPoint').windowOff(); - $el.data('focusPoint', fp); - if (settings.reCalcOnWindowResize) fp.windowOn(); - }); + // Chain jQuery functions + return this; }; - $.fn.adjustFocus = function() { - //Deprecated v1.2 - return this.each(function() { - adjustFocus($(this)); - }); - }; - -})(jQuery); \ No newline at end of file +})( jQuery, window, document ); diff --git a/js/jquery.focuspoint.min.js b/js/jquery.focuspoint.min.js index b3a0da9..d18d57b 100644 --- a/js/jquery.focuspoint.min.js +++ b/js/jquery.focuspoint.min.js @@ -1,8 +1,8 @@ /** - * jQuery FocusPoint; version: 1.1.3 - * Author: http://jonathonmenz.com + * jQuery FocusPoint; version: v2-dev + * Author: J. Menz http://jonathonmenz.com * Source: https://github.com/jonom/jquery-focuspoint - * Copyright (c) 2014 J. Menz; MIT License + * Copyright (c) 2014 - 2016 J. Menz; License TBC * @preserve */ -!function($){var t={reCalcOnWindowResize:!0,throttleDuration:17},n=function(t){var n=t.find("img").attr("src");t.data("imageSrc",n),i(n,function(n,i){t.data({imageW:i.width,imageH:i.height}),r(t)})},i=function(t,n){$("").one("load",function(){n(null,{width:this.width,height:this.height})}).attr("src",t)},a=function(t,n){var i=!1;return function(){var a=Array.prototype.slice.call(arguments,0);if(i)return!1;i=!0,setTimeout(function(){i=!1,t.apply(null,a)},n)}},o=function(t,n,i,a,o){var r=Math.floor(n/2),e=(a+1)/2,u=Math.floor(i/t),f=Math.floor(e*u);o&&(f=u-f);var c=f-r,s=u-f,h=n-r;return s<0&&(c=0),-100*c/n+"%"},r=function(t){var i=t.data("imageW"),a=t.data("imageH"),r=t.data("imageSrc");if(!i&&!a&&!r)return n(t);var e=t.width(),u=t.height(),f=parseFloat(t.data("focusX")),c=parseFloat(t.data("focusY")),s=t.find("img").first(),h=0,d=0;if(!(e>0&&u>0&&i>0&&a>0))return!1;var l=i/e,w=a/u;s.css({"max-width":"","max-height":""}),i>e&&a>u&&s.css(l>w?"max-height":"max-width","100%"),l>w?h=o(w,e,i,f):l0){var e=-1*a,n=.5-i*t;return n>0?0:n<0&&n1?(n.sort(m),s.start=n[0],s.stop=n[1],s.average=(n[0]+n[1])/2):s.start=s.stop=s.average=n[0],s.maxScale=1/(s.stop-s.start),s};i.extend(n.prototype,{init:function(){if(this.$el=i(this.element),this.$image=this.$el.find("img, video").first(),this.imageW=this.$el.data("imageW"),this.imageH=this.$el.data("imageH"),this.imageRatio=this.imageW/this.imageH,!this.imageW&&!this.imageH)return this.resolveImageSize();var t=this;if(i.each(["X","Y"],(function(i,a){var e=u(a,String(t.$el.data("focus"+a)),t.settings.legacyGrid);t["focus"+a+"MinStart"]=e.start,t["focus"+a+"MinStop"]=e.stop,t["focus"+a]=e.average,t["minCroppingRegionMaxScale"+a]=e.maxScale,e=u(a,String(t.$el.data("focus"+a+"Ideal")),t.settings.legacyGrid),t["focus"+a+"IdealStart"]=e.start,t["focus"+a+"IdealStop"]=e.stop,t["idealCroppingRegionMaxScale"+a]=e.maxScale})),this.minCroppingRegionW=(this.focusXMinStop-this.focusXMinStart)*this.imageW,this.minCroppingRegionH=(this.focusYMinStop-this.focusYMinStart)*this.imageH,this.minCroppingRegionRatio=this.minCroppingRegionW/this.minCroppingRegionH,this.idealCroppingRegionW=(this.focusXIdealStop-this.focusXIdealStart)*this.imageW,this.idealCroppingRegionH=(this.focusYIdealStop-this.focusYIdealStart)*this.imageH,this.idealCroppingRegionRatio=this.idealCroppingRegionW/this.idealCroppingRegionH,this.settings.setTransformOrigin){var a=100*t.focusX+"% "+100*t.focusY+"%";this.$image.css({"webkit-transform-origin":a,"transform-origin":a})}this.$el.addClass("focuspoint-active"),this.adjustFocus(),this.settings.reCalcOnWindowResize&&this.windowOn()},resolveImageSize:function(){if(this.triedAutoResolution)return!1;this.triedAutoResolution=!0;var t=this.$el;i("").one("load",(function(){t.data("imageW",this.width),t.data("imageH",this.height),t.focusPoint("init")})).attr("src",this.$image.attr("src"))},windowOn:function(){o=o.add(this.element),l()},windowOff:function(){o=o.not(this.element),l()},adjustFocus:function(){var i={};if(i.containerW=this.$el.width(),i.containerH=this.$el.height(),!(i.containerW>0&&i.containerH>0&&this.imageW>0&&this.imageH>0))return!1;i.containerRatio=i.containerW/i.containerH,i.containerRatio!==this.imageRatio?(i.axisScale={X:this.imageRatio/i.containerRatio,Y:i.containerRatio/this.imageRatio},i.containerRatio>=this.idealCroppingRegionRatio&&i.containerRatio<=this.imageW/this.idealCroppingRegionH?(i.clippingMode="ideal",i.primaryClippingAxis="Y",i.secondaryClippingAxis="X"):i.containerRatio<=this.idealCroppingRegionRatio&&i.containerRatio>=this.idealCroppingRegionW/this.imageH?(i.clippingMode="ideal",i.primaryClippingAxis="X",i.secondaryClippingAxis="Y"):this.imageRatio>i.containerRatio?(i.clippingMode="classic",i.primaryClippingAxis="X",i.axisScale.X>this.minCroppingRegionMaxScaleX&&(i.clippingMode="min")):(i.clippingMode="classic",i.primaryClippingAxis="Y",i.axisScale.Y>this.minCroppingRegionMaxScaleY&&(i.clippingMode="min")),i.scale=1,i.shiftPrimary=0,i.shiftSecondary=0,"ideal"===i.clippingMode?(i.scale=this["idealCroppingRegionMaxScale"+i.primaryClippingAxis]/i.axisScale[i.primaryClippingAxis],i.shiftSecondary=100*f(this["focus"+i.secondaryClippingAxis],i.scale)+"%",i.shiftPrimary=-100*this["focus"+i.primaryClippingAxis+"IdealStart"]*i.axisScale[i.primaryClippingAxis]*i.scale+"%"):"min"===i.clippingMode?(i.scale=this["minCroppingRegionMaxScale"+i.primaryClippingAxis]/i.axisScale[i.primaryClippingAxis],i.shiftSecondary=100*f(this["focus"+i.secondaryClippingAxis],i.scale)+"%",i.shiftPrimary=-100*this["focus"+i.primaryClippingAxis+"MinStart"]*i.axisScale[i.primaryClippingAxis]*i.scale+"%"):i.shiftPrimary=100*f(this["focus"+i.primaryClippingAxis],i.axisScale[i.primaryClippingAxis])+"%","X"===i.primaryClippingAxis?this.$image.css({width:i.axisScale[i.primaryClippingAxis]*i.scale*100+"%",height:100*i.scale+"%",left:i.shiftPrimary,top:i.shiftSecondary}):this.$image.css({width:100*i.scale+"%",height:i.axisScale[i.primaryClippingAxis]*i.scale*100+"%",left:i.shiftSecondary,top:i.shiftPrimary})):this.$image.css({width:"100%",height:"100%",left:0,top:0})}}),i.fn.focusPoint=function(t){return this.each((function(){i.data(this,"focusPoint")||i.data(this,"focusPoint",new n(this,t))})),"string"==typeof t?this.each((function(){var a;i(this).data("focusPoint")[t]()})):this}}(jQuery,window,document); \ No newline at end of file