├── package.json ├── bower.json ├── .gitignore ├── minimap.jquery.json ├── LICENSE ├── dist ├── minimap.min.css └── minimap.min.js ├── Gruntfile.js ├── src ├── minimap.css └── minimap.js └── README.md /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "minimap.js", 3 | "version": "1.0.0", 4 | "description": "A preview of full web page - a jQuery plugin", 5 | "devDependencies": { 6 | "grunt": "~0.4.5", 7 | "grunt-contrib-jshint": "~0.10.0", 8 | "grunt-contrib-nodeunit": "~0.4.1", 9 | "grunt-contrib-uglify": "~0.5.0", 10 | "grunt-postcss": "~0.1.0", 11 | "grunt-contrib-cssmin" : "*", 12 | "grunt-autoprefixer" : "*" 13 | }, 14 | "keywords": [ 15 | "navigation", 16 | "preview", 17 | "minimap", 18 | "mini-map" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "minimap", 3 | "version": "1.1.0", 4 | "homepage": "https://github.com/princejwesley/minimap", 5 | "authors": [ 6 | "princejwesley " 7 | ], 8 | "main" : "dist/minimap.min.js", 9 | "license": "MIT", 10 | "ignore": [ 11 | "!(dist)" 12 | ], 13 | "description" : "Minimap - A preview of full web page or its DOM element| a jquery plugin", 14 | "dependencies": { 15 | "jquery": ">=1.10.2" 16 | }, 17 | "keywords": [ 18 | "navigation", 19 | "preview", 20 | "minimap", 21 | "mini-map" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | bower_components 31 | -------------------------------------------------------------------------------- /minimap.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "minimap", 3 | "version": "1.0.0", 4 | "title": "A preview of full web page", 5 | "description" : "A preview of full web page", 6 | "author": { 7 | "name" : "Prince John Wesley", 8 | "email" : "princejohnwesley@gmail.com", 9 | "url" : "http://www.toolitup.com" 10 | }, 11 | "licenses": [ 12 | { 13 | "type" : "MIT", 14 | "url" : "https://github.com/princejwesley/minimap/blob/master/LICENSE" 15 | } 16 | ], 17 | "dependencies" : { 18 | "jquery": ">=1.8" 19 | }, 20 | "homepage" : "https://github.com/princejwesley/minimap", 21 | "docs" : "https://github.com/princejwesley/minimap", 22 | "demo" : "http://www.toolitup.com/minimap.html", 23 | "keywords" : ["minimap", "preview", "page-preview"], 24 | "bugs" : "https://github.com/princejwesley/minimap/issues" 25 | 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Prince John Wesley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /dist/minimap.min.css: -------------------------------------------------------------------------------- 1 | /*! The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Prince John Wesley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | **/.minimap,.miniregion{position:fixed;cursor:pointer}.minimap{z-index:10000;font-size:1em}.miniregion{z-index:10001;background:0 0;border:2px solid silver;border-radius:10%;cursor:-webkit-grab;cursor:-moz-grab}.miniregion.dragging{cursor:-webkit-grabbing;cursor:-moz-grabbing}.miniregion:hover{box-shadow:0 0 .4em #a9a9a9}.noselect{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} 24 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | uglify: { 5 | options: { 6 | mangle: true, 7 | sourceMap: false, 8 | preserveComments: 'some', 9 | }, 10 | dynamic_mappings: { 11 | files: [{ 12 | src: 'src/minimap.js', 13 | dest: 'dist/minimap.min.js', 14 | }, ], 15 | }, 16 | }, 17 | 18 | autoprefixer: { 19 | 20 | options: { 21 | diff: false, 22 | map: false, 23 | browsers: ['> 1%', 'last 5 versions', 'Firefox ESR', 'Opera 12.1'] 24 | }, 25 | prefixed_css: { 26 | src: 'src/minimap.css', 27 | dest: 'dist/minimap.min.css', 28 | }, 29 | 30 | }, 31 | jshint: { 32 | all: ['Gruntfile.js', 'src/*.js'], 33 | options: { 34 | multistr: true 35 | } 36 | }, 37 | cssmin: { 38 | my_target: { 39 | options: { 40 | keepSpecialComments: "*" 41 | }, 42 | files: [{ 43 | src: 'dist/minimap.min.css', 44 | dest: 'dist/minimap.min.css', 45 | ext: '.min.css' 46 | }] 47 | } 48 | } 49 | }); 50 | 51 | // Load the plugin that provides the "uglify" task. 52 | grunt.loadNpmTasks('grunt-contrib-uglify'); 53 | grunt.loadNpmTasks('grunt-autoprefixer'); 54 | grunt.loadNpmTasks('grunt-contrib-jshint'); 55 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 56 | 57 | 58 | // Default task(s). 59 | grunt.registerTask('default', ['jshint', 'uglify', 'autoprefixer', 'cssmin']); 60 | 61 | }; 62 | -------------------------------------------------------------------------------- /src/minimap.css: -------------------------------------------------------------------------------- 1 | /*! The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Prince John Wesley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | **/ 24 | 25 | .minimap { 26 | z-index : 10000; 27 | position : fixed; 28 | cursor: pointer; 29 | font-size: 1em; 30 | } 31 | 32 | .miniregion { 33 | z-index : 10001; 34 | position : fixed; 35 | background : transparent; 36 | border: 2px solid silver; 37 | border-radius: 10%; 38 | cursor: pointer; 39 | cursor: -webkit-grab; 40 | cursor: -moz-grab; 41 | } 42 | 43 | .miniregion.dragging { 44 | cursor: -webkit-grabbing; 45 | cursor: -moz-grabbing; 46 | } 47 | 48 | .miniregion:hover { 49 | box-shadow: 0 0 0.4em darkgrey; 50 | } 51 | 52 | .noselect { 53 | -webkit-touch-callout: none; 54 | -webkit-user-select: none; 55 | -khtml-user-select: none; 56 | -moz-user-select: none; 57 | -ms-user-select: none; 58 | user-select: none; 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [minimap](http://www.toolitup.com/minimap.html) - A jQuery Plugin 2 | =============== 3 | A preview of full webpage or its DOM element with flexible positioning and navigation support 4 | #####[Demo Page](http://www.toolitup.com/minimap.html) 5 | [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/princejwesley/minimap) 6 | 7 | ## Getting Started 8 | 9 | ### Download the latest code 10 | 11 | 12 | [Fork](https://github.com/princejwesley/minimap) this repository or download js/css files from `dist` directory. 13 | 14 | ### Including it on your page 15 | 16 | Include jQuery and this plugin on a page. 17 | 18 | ```html 19 | 20 | 21 | 22 | ``` 23 | 24 | ### Basic Usage 25 | ```javascript 26 | //Desired dom element 27 | var previewBody = $('body').minimap(); 28 | 29 | ``` 30 | ### Properties 31 | #### heightRatio 32 | > `height` ratio of the view port. ratio can be in the range [0.0, 1.0). (*default: **0.6***) 33 | 34 | #### widthRatio 35 | > `width` ratio of the view port. ratio can be in the range [0.0, 0.5). (*default: **0.05***) 36 | 37 | #### offsetHeightRatio 38 | > Margin `top` ratio of the view port. ratio can be in the range (0.0, 0.9]. (*default: **0.035***) 39 | 40 | #### offsetWidthRatio 41 | > Margin `left` or `right`(*based on `position` property*) ratio of the view port. ratio can be in the range (0.0, 0.9]. (*default: **0.035***) 42 | 43 | 44 | #### position 45 | > `position` of the minimap. Supported positions are: 46 | 47 | 1. `'right'` (*default*) 48 | 2. `'left'` 49 | 50 | ### touch 51 | > `touch` support. (default: *true*) 52 | 53 | ### smoothScroll 54 | >linear `animation` support for scrolling. (dafault: *true*) 55 | 56 | ### smoothScrollDelay 57 | > Smooth scroll delay in milliseconds. (default: 200ms) 58 | 59 | ### disableFind 60 | > `disableFind` if true, prevents browser CTRL+F from finding duplicated text in minimap. (default: *false*) 61 | 62 | ## Setters 63 | ### function setPosition(position) 64 | > Set `position` property. `position` can be either `'left'` or `'right'` 65 | 66 | ### function setHeightRatio(ratio) 67 | > Set `heightRatio` property. 68 | 69 | ### function setWidthRatio(ratio) 70 | > Set `widthRatio` property. 71 | 72 | ### function setOffsetHeightRatio(ratio) 73 | > Set `offsetHeightRatio` property. 74 | 75 | ### function setOffsetWidthRatio(ratio) 76 | > Set `offsetWidthRatio` property. 77 | 78 | ### function setSmoothScroll(smooth) 79 | > Set `smoothScroll` property 80 | 81 | ### function setSmoothScrollDelay(duration) 82 | > Set `setSmoothScrollDelay` property. 83 | 84 | ## Callback 85 | ### function onPreviewChange(minimap, scale) 86 | > `onPreviewChange` callback will be triggered for the below cases: 87 | 88 | 1. View port is resized. 89 | 2. Calling setter functions. 90 | 91 | Use this function to *customize* DOMs inside minimap. 92 | 93 | Parameters: 94 | ``` 95 | minimap - $minimap DOM 96 | scale - Scale object with `x` and `y` properties.(width/height ratio of minimap with respect to viewport) 97 | ``` 98 | ## Other functions 99 | ### function show() 100 | > Show preview 101 | 102 | ### function hide() 103 | > Hide preview 104 | 105 | ### function toggle() 106 | > Toggle Preview 107 | 108 | ### Default Settings 109 | Mini-map with default values 110 | ```javascript 111 | var previewBody = $('body').minimap( 112 | heightRatio : 0.6, 113 | widthRatio : 0.05, 114 | offsetHeightRatio : 0.035, 115 | offsetWidthRatio : 0.035, 116 | position : "right", 117 | touch: true, 118 | smoothScroll: true, 119 | smoothScrollDelay: 200, 120 | onPreviewChange: function(minimap, scale) {}, 121 | disableFind : false 122 | }); 123 | ``` 124 | 125 | #### CSS classes 126 | Use the below css classes for customization 127 | > `.minimap` - Mini-map area 128 | 129 | > `.miniregion` - Mini-map view area 130 | 131 | ## Caveats 132 | 1. Async updates to the dom elements after minimap was created may not reflect in the preview. 133 | 134 | ## License 135 | This plugin is licensed under the [MIT license](https://github.com/princejwesley/minimap/blob/master/LICENSE). 136 | 137 | Copyright (c) 2014 [Prince John Wesley](http://www.toolitup.com) 138 | -------------------------------------------------------------------------------- /dist/minimap.min.js: -------------------------------------------------------------------------------- 1 | /*! The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Prince John Wesley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | **/ 24 | !function(t,o){"use strict";t.fn.minimap=function(o){var e=this,i=t(window),n=function(){},a=!0,s={heightRatio:.6,widthRatio:.05,offsetHeightRatio:.035,offsetWidthRatio:.035,position:"right",touch:!0,smoothScroll:!0,smoothScrollDelay:200,onPreviewChange:n,disableFind:!1},r=t.extend({},s,o),h=["right","left"];jQuery.fn.disableFind=function(){return this.each(function(){for(var o="",e=!1,i=t(this),n=i.html(),a=0;a"===n[a]&&(e=!1),e===!1&&(o+='.')," "===n[a]&&(o+=" ");i.html(o)})};var c=function(o,e){switch(o){case"disableFind":if(1!=e&&0!=e)throw"Invalid disableFind: "+e;break;case"heightRatio":var i=e;if(!t.isNumeric(i)||0>=i||i>1)throw"Invalid heightRatio: "+i;break;case"widthRatio":var n=e;if(!t.isNumeric(n)||0>=n||n>.5)throw"Invalid widthRatio: "+n;break;case"offsetHeightRatio":var a=e;if(!t.isNumeric(a)||0>a||a>.9)throw"Invalid offsetHeightRatio: "+a;break;case"offsetWidthRatio":var s=e;if(!t.isNumeric(s)||0>s||s>.9)throw"Invalid offsetWidthRatio: "+s;break;case"position":var c=e.toLowerCase(),l=h.indexOf(c);if(-1===l)throw"Invalid position: "+r.position;r.position=c;break;case"smoothScrollDelay":var d=e;if((0|d)!==d||4>d)throw"Invalid smoothScrollDelay(in ms): "+d;break;case"touch":case"smoothScroll":break;case"onPreviewChange":var f=e;if(!f||!t.isFunction(f))throw"Invalid onPreviewChange: "+e;break;default:throw"Invalid validation property: "+o}};for(var l in r)c(l,r[l]);var d=e.clone();d.find(".minimap.noselect").remove(),d.find(".miniregion").remove(),d.addClass("minimap noselect"),r.disableFind===!0&&d.children().each(function(){t(this).addClass("unsearchable")}),d.children().each(function(){t(this).css({"pointer-events":"none"})});var f=t('
');t(t("body")[0]).append(f),t(t("body")[0]).append(d),t(".unsearchable").disableFind();var u=function(){return{x:i.width()/e.width()*r.widthRatio,y:i.height()/e.height()*r.heightRatio}},v=function(t){if(a){var o=u(),n="scale("+o.x+","+o.y+")",s=i.height()*r.offsetHeightRatio,h=i.width()*r.offsetWidthRatio,c=e.height()*(o.y-1)/2+s,l=e.width()*(o.x-1)/2+h,v=i.width()*(1/o.x)*r.widthRatio,m=i.height()*(1/o.y)*r.heightRatio,p={"-webkit-transform":n,"-moz-transform":n,"-ms-transform":n,"-o-transform":n,transform:n,top:c,width:v,height:m,margin:"0px",padding:"0px"};p[r.position]=l,d.css(p);var g=e.offset().top*o.y,w={width:d.width()*o.x,height:i.height()*o.y,margin:"0px",top:i.scrollTop()*o.y+s-g+"px"};w[r.position]=h+"px",f.css(w),r.onPreviewChange(d,o)}},m=function(t){if(a){var o=u(),n=i.height()*r.offsetHeightRatio,s=e.offset().top*o.y,h=i.scrollTop()*o.y,c=f.outerHeight(!0),l=e.outerHeight(!0)*o.y+s;s>h+c+n||h>l?f.css({display:"none"}):f.css({top:h+n-s+"px",display:"block"})}},p=function(t){if(a){var o=u(),n=i.height()*r.offsetHeightRatio,s=e.offset().top*o.y,h=f.outerHeight(!0),c=(t.clientY-h/2-n+s)/o.y;if("click"===t.type&&r.smoothScroll){var l=i.scrollTop(),d=e.outerHeight(!0);c=Math.max(c,Math.min(c,d));var v=c>l,m=r.smoothScrollDelay,p=Math.abs(l-c),g=m/p,y=1,R=4;g>=4?R=parseInt(y):y=g>=1?4*parseInt(g):4/g;var b=l,x=parseInt(p/y);w=!0;var k=function(){b+=v?y:-y,--x<=0&&(clearInterval(I),w=!1,b=c),i.scrollTop(b)},I=window.setInterval(k,R)}else i.scrollTop(c);t.stopPropagation()}},g=!1,w=!1,y=function(t){g=!1,e.removeClass("noselect"),f.removeClass("dragging")},R=function(t){g&&!w&&p(t)},b=function(t){p(t),g=!1},x=function(t){g=!0,e.addClass("noselect"),f.addClass("dragging")};v(),i.on("resize",v),i.on("scroll",m),t(document).on("mouseup",y),t(document).on("mousemove",R),t(f).on("mousedown",x),t(f).on("mouseup",y),t(f).on("mousemove",R),t(f).on("click",b),t(d).on("mousedown",x),t(d).on("mouseup",y),t(d).on("mousemove",R),t(d).on("click",b);var k="",I=function(t){var o=t.changedTouches;if(!(o.length>1)){var e=o[0],i=["touchstart","touchmove","touchend"],n=["mousedown","mousemove","mouseup"],a=i.indexOf(t.type);if(-1!==a){var s=n[a];t.type===i[2]&&k===i[0]&&(s="click");var r=document.createEvent("MouseEvent");r.initMouseEvent(s,!0,!0,window,1,e.screenX,e.screenY,e.clientX,e.clientY,!1,!1,!1,!1,0,null),e.target.dispatchEvent(r),t.preventDefault(),k=t.type}}};r.touch&&(document.addEventListener("touchstart",I,!0),document.addEventListener("touchmove",I,!0),document.addEventListener("touchend",I,!0),document.addEventListener("touchcancel",I,!0));var H=function(t){var o=r.position;if(c("position",t),o!==r.position){var e={};e[o]="",v(),f.css(e),d.css(e)}},S=function(t,o){return function(e){c(t,e),r[t]=e,o&&v()}},C=function(){a||(d.show(),f.show(),a=!0,v())},E=function(){a&&(d.hide(),f.hide(),a=!1)},D=function(){d.toggle(),f.toggle(),a=!a,a&&v()};return t.extend({},this,{setPosition:H,setHeightRatio:S("heightRatio",!0),setWidthRatio:S("widthRatio",!0),setOffsetHeightRatio:S("offsetHeightRatio",!0),setOffsetWidthRatio:S("offsetWidthRatio",!0),setSmoothScroll:S("smoothScroll"),setSmoothScrollDelay:S("smoothScrollDelay"),show:C,hide:E,toggle:D})}}(jQuery); 25 | -------------------------------------------------------------------------------- /src/minimap.js: -------------------------------------------------------------------------------- 1 | /*! The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Prince John Wesley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | **/ 24 | 25 | (function($, undefined) { 26 | 27 | "use strict"; 28 | $.fn.minimap = function(options) { 29 | var minimap = this; 30 | var $window = $(window); 31 | var fn = function() {}; 32 | var shown = true; 33 | 34 | var defaults = { 35 | heightRatio : 0.6, 36 | widthRatio : 0.05, 37 | offsetHeightRatio : 0.035, 38 | offsetWidthRatio : 0.035, 39 | position : "right", 40 | touch: true, 41 | smoothScroll: true, 42 | smoothScrollDelay: 200, 43 | onPreviewChange: fn, 44 | disableFind : false 45 | }; 46 | var settings = $.extend({}, defaults, options); 47 | var position = ["right", "left"]; 48 | 49 | //when invoked, this function prevents browsers from finding 50 | //duplicate text located in the minimap 51 | jQuery.fn.disableFind = function(){ 52 | return this.each(function (){ 53 | var newHTML = ""; // create a new blank string 54 | var stop = false; // boolean to toggle whether we're in a tag or not 55 | var currentElement = $(this); // variable to hold the current element 56 | var html = currentElement.html(); // get html from current element 57 | for (var i = 0; i < html.length; i++) // iterate through each character of the html 58 | { 59 | newHTML += html[i]; // insert current character into newHTML 60 | if (html[i] === '<') { stop = true }; // stop when entering a tag 61 | if (html[i] === '>') { stop = false }; // continue when exiting a tag 62 | if (stop === false) { // inject dot into newHTML 63 | newHTML += ''+ '.' +''; 64 | } 65 | if (html[i] === ' ') { newHTML += ' '; } // insert a space if the current character is a space 66 | } 67 | currentElement.html(newHTML); // replace current element with newHTML 68 | }); 69 | }; 70 | 71 | var validateProps = function(prop, value) { 72 | 73 | switch(prop) { 74 | case 'disableFind': 75 | if(value != true && value != false) 76 | throw "Invalid disableFind: " + value; 77 | break; 78 | case 'heightRatio': 79 | var heightRatio = value; 80 | if(!$.isNumeric(heightRatio) || heightRatio <= 0.0 || heightRatio > 1.0) 81 | throw "Invalid heightRatio: " + heightRatio; 82 | break; 83 | case 'widthRatio': 84 | var widthRatio = value; 85 | if(!$.isNumeric(widthRatio) || widthRatio <= 0.0 || widthRatio > 0.5) 86 | throw "Invalid widthRatio: " + widthRatio; 87 | break; 88 | case 'offsetHeightRatio': 89 | var offsetHeightRatio = value; 90 | if(!$.isNumeric(offsetHeightRatio) || offsetHeightRatio < 0.0 || offsetHeightRatio > 0.9) 91 | throw "Invalid offsetHeightRatio: " + offsetHeightRatio; 92 | break; 93 | case 'offsetWidthRatio': 94 | var offsetWidthRatio = value; 95 | if(!$.isNumeric(offsetWidthRatio) || offsetWidthRatio < 0.0 || offsetWidthRatio > 0.9) 96 | throw "Invalid offsetWidthRatio: " + offsetWidthRatio; 97 | break; 98 | case 'position': 99 | var p = value.toLowerCase(); 100 | var pos = position.indexOf(p); 101 | if(pos === -1) throw "Invalid position: " + settings.position; 102 | settings.position = p; 103 | break; 104 | case 'smoothScrollDelay': 105 | var smoothScrollDelay = value; 106 | if(((smoothScrollDelay | 0 ) !== smoothScrollDelay) || smoothScrollDelay < 4) 107 | throw "Invalid smoothScrollDelay(in ms): " + smoothScrollDelay; 108 | break; 109 | case 'touch': 110 | case 'smoothScroll': 111 | break; 112 | case 'onPreviewChange': 113 | var fn = value; 114 | if(!fn || !$.isFunction(fn)) 115 | throw "Invalid onPreviewChange: " + value; 116 | break; 117 | default: 118 | throw "Invalid validation property: " + prop; 119 | } 120 | }; 121 | 122 | //validate inputs 123 | for(var prop in settings) validateProps(prop, settings[prop]); 124 | 125 | var miniElement = minimap.clone(); 126 | 127 | miniElement.find('.minimap.noselect').remove(); 128 | miniElement.find('.miniregion').remove(); 129 | miniElement.addClass('minimap noselect'); 130 | 131 | //add the class that will be targeted by disableFind : true 132 | if(settings.disableFind === true){ 133 | miniElement.children().each(function() {$(this).addClass('unsearchable');}); 134 | } 135 | 136 | // remove events & customized cursors 137 | miniElement.children().each(function() {$(this).css({'pointer-events': 'none'});}); 138 | 139 | var region = $('
'); 140 | 141 | $($('body')[0]).append(region); 142 | $($('body')[0]).append(miniElement); 143 | 144 | //invoke the function that prevents browser from finding duplicate text in minimap 145 | $('.unsearchable').disableFind(); 146 | 147 | var scale = function() { 148 | return { 149 | x: ($window.width() / minimap.width()) * settings.widthRatio, 150 | y: ($window.height() / minimap.height()) * settings.heightRatio 151 | }; 152 | }; 153 | 154 | var onResizeHandler = function(e) { 155 | if(!shown) return; 156 | 157 | var s = scale(); 158 | var sc = 'scale(' + s.x + ','+ s.y + ')'; 159 | var offsetTop = $window.height() * settings.offsetHeightRatio; 160 | 161 | var offsetLeftRight = $window.width() * settings.offsetWidthRatio; 162 | 163 | var top = minimap.height() * (s.y - 1) / 2 + offsetTop; 164 | var leftRight = minimap.width() * (s.x - 1) / 2 + offsetLeftRight; 165 | 166 | var width = $window.width() * (1/s.x) * settings.widthRatio; 167 | var height = $window.height() * (1/s.y) * settings.heightRatio; 168 | 169 | var css = { 170 | '-webkit-transform': sc, 171 | '-moz-transform': sc, 172 | '-ms-transform': sc, 173 | '-o-transform': sc, 174 | 'transform': sc, 175 | 'top' : top, 176 | 'width' : width, 177 | 'height' : height, 178 | 'margin' : '0px', 179 | 'padding' : '0px' 180 | }; 181 | css[settings.position] = leftRight; 182 | 183 | miniElement.css(css); 184 | 185 | var regionTop = minimap.offset().top * s.y; 186 | var cssRegion = { 187 | width : miniElement.width() * s.x, 188 | height : $window.height() * s.y, 189 | margin : '0px', 190 | top : $window.scrollTop() * s.y + offsetTop - regionTop + 'px' 191 | }; 192 | cssRegion[settings.position] = offsetLeftRight + 'px'; 193 | region.css(cssRegion); 194 | 195 | settings.onPreviewChange(miniElement, s); 196 | }; 197 | 198 | var onScrollHandler = function(e) { 199 | if(!shown) return; 200 | 201 | var s = scale(); 202 | var offsetTop = $window.height() * settings.offsetHeightRatio; 203 | var top = minimap.offset().top * s.y; 204 | var pos = ($window.scrollTop()) * s.y; 205 | var regionHeight = region.outerHeight(true); 206 | var bottom = minimap.outerHeight(true) * s.y + top;// - regionHeight; 207 | 208 | if(pos + regionHeight + offsetTop < top || pos > bottom) { 209 | region.css({ 210 | display: 'none', 211 | }); 212 | } else { 213 | region.css({ 214 | top : pos + offsetTop - top + 'px', 215 | display : 'block' 216 | }); 217 | } 218 | }; 219 | 220 | var scrollTop = function(e) { 221 | if(!shown) return; 222 | 223 | var s = scale(); 224 | var offsetTop = $window.height() * settings.offsetHeightRatio; 225 | var top = minimap.offset().top * s.y; 226 | var regionHeight = region.outerHeight(true); 227 | var target = (e.clientY - regionHeight/2 - offsetTop + top) / s.y; 228 | 229 | if(e.type === 'click' && settings.smoothScroll) { 230 | var current = $window.scrollTop(); 231 | var maxTarget = minimap.outerHeight(true); 232 | target = Math.max(target, Math.min(target, maxTarget)); 233 | var direction = target > current; 234 | var delay = settings.smoothScrollDelay; 235 | var distance = Math.abs(current - target); 236 | var r = delay / distance; 237 | var unitScroll = 1; 238 | var unitDelay = 4; 239 | if(r >= 4) { 240 | unitDelay = parseInt(unitScroll); 241 | } else if(r >= 1) { 242 | unitScroll = parseInt(r) * 4; 243 | } else { 244 | unitScroll = (4 / r); 245 | } 246 | 247 | var next = current; 248 | var count = parseInt(distance / unitScroll); 249 | onSmoothScroll = true; 250 | 251 | // linear translate 252 | var smoothScroll = function() { 253 | next = next + (direction ? unitScroll : -unitScroll); 254 | if(--count <= 0) { 255 | clearInterval(timer); 256 | onSmoothScroll = false; 257 | next = target; 258 | } 259 | $window.scrollTop(next); 260 | }; 261 | var timer = window.setInterval(smoothScroll, unitDelay); 262 | } else { 263 | $window.scrollTop(target); 264 | } 265 | e.stopPropagation(); 266 | }; 267 | 268 | var mousedown = false; 269 | var onSmoothScroll = false; 270 | var onMouseupHandler = function(e) { 271 | mousedown = false; 272 | minimap.removeClass('noselect'); 273 | region.removeClass('dragging'); 274 | }; 275 | 276 | var onMousemoveHandler = function(e) { 277 | if(!mousedown || onSmoothScroll) return; 278 | scrollTop(e); 279 | }; 280 | 281 | var onClickHandler = function(e) { 282 | scrollTop(e); 283 | mousedown= false; 284 | }; 285 | 286 | var onMousedownHandler = function(e) { 287 | mousedown = true; 288 | minimap.addClass('noselect'); 289 | region.addClass('dragging'); 290 | }; 291 | 292 | onResizeHandler(); 293 | $window.on('resize', onResizeHandler); 294 | $window.on('scroll', onScrollHandler); 295 | 296 | $(document).on('mouseup', onMouseupHandler); 297 | $(document).on('mousemove', onMousemoveHandler); 298 | 299 | $(region).on('mousedown', onMousedownHandler); 300 | $(region).on('mouseup', onMouseupHandler); 301 | $(region).on('mousemove', onMousemoveHandler); 302 | $(region).on('click', onClickHandler); 303 | 304 | $(miniElement).on('mousedown', onMousedownHandler); 305 | $(miniElement).on('mouseup', onMouseupHandler); 306 | $(miniElement).on('mousemove', onMousemoveHandler); 307 | $(miniElement).on('click', onClickHandler); 308 | 309 | var lastTouchType = ''; 310 | var touchHandler = function(e) { 311 | var touches = e.changedTouches; 312 | 313 | // Ignore multi-touch 314 | if (touches.length > 1) return; 315 | 316 | var touch = touches[0]; 317 | var events = ["touchstart", "touchmove", "touchend"]; 318 | var mouseEvents = ["mousedown", "mousemove", "mouseup"]; 319 | var ev = events.indexOf(e.type); 320 | 321 | if (ev === -1) return; 322 | 323 | var type = mouseEvents[ev]; 324 | if (e.type === events[2] && lastTouchType === events[0]) { 325 | type = "click"; 326 | } 327 | 328 | 329 | var simulatedEvent = document.createEvent("MouseEvent"); 330 | simulatedEvent.initMouseEvent(type, true, true, window, 1, 331 | touch.screenX, touch.screenY, 332 | touch.clientX, touch.clientY, false, 333 | false, false, false, 0, null); 334 | touch.target.dispatchEvent(simulatedEvent); 335 | e.preventDefault(); 336 | lastTouchType = e.type; 337 | }; 338 | 339 | if (settings.touch) { 340 | 341 | document.addEventListener("touchstart", touchHandler, true); 342 | document.addEventListener("touchmove", touchHandler, true); 343 | document.addEventListener("touchend", touchHandler, true); 344 | document.addEventListener("touchcancel", touchHandler, true); 345 | 346 | } 347 | 348 | var setPosition = function(pos) { 349 | var oldValue = settings.position; 350 | validateProps('position', pos); 351 | if(oldValue !== settings.position) { 352 | var css = {}; 353 | css[oldValue] = ''; 354 | onResizeHandler(); 355 | region.css(css); 356 | miniElement.css(css); 357 | } 358 | }; 359 | 360 | var setProperty = function(propName, redraw) { 361 | return function(value) { 362 | validateProps(propName, value); 363 | settings[propName] = value; 364 | if(redraw) onResizeHandler(); 365 | }; 366 | }; 367 | 368 | var show = function() { 369 | if(shown) return; 370 | miniElement.show(); 371 | region.show(); 372 | shown = true; 373 | onResizeHandler(); 374 | }; 375 | 376 | var hide = function() { 377 | if(!shown) return; 378 | miniElement.hide(); 379 | region.hide(); 380 | shown = false; 381 | }; 382 | 383 | var toggle = function() { 384 | miniElement.toggle(); 385 | region.toggle(); 386 | shown = !shown; 387 | if(shown) onResizeHandler(); 388 | }; 389 | 390 | return $.extend({}, this, { 391 | "setPosition": setPosition, 392 | "setHeightRatio": setProperty('heightRatio', true), 393 | "setWidthRatio": setProperty('widthRatio', true), 394 | "setOffsetHeightRatio": setProperty('offsetHeightRatio', true), 395 | "setOffsetWidthRatio": setProperty('offsetWidthRatio', true), 396 | "setSmoothScroll" : setProperty('smoothScroll'), 397 | "setSmoothScrollDelay" : setProperty('smoothScrollDelay'), 398 | "show" : show, 399 | "hide" : hide, 400 | "toggle" : toggle 401 | }); 402 | 403 | }; 404 | }(jQuery)); 405 | --------------------------------------------------------------------------------