├── .gitignore ├── logo ├── logo.png ├── text.png └── logos.sketch ├── test ├── spin.gif └── index.html ├── .travis.yml ├── LICENSE ├── dist ├── css │ ├── threeFingerTap.min.css │ └── threeFingerTap.css └── js │ ├── threeFingerTap.min.js │ └── threeFingerTap.js ├── tests └── init.test.js ├── src ├── scss │ └── threeFingerTap.scss └── js │ └── index.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidthesloth92/three-finger-tap-js/HEAD/logo/logo.png -------------------------------------------------------------------------------- /logo/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidthesloth92/three-finger-tap-js/HEAD/logo/text.png -------------------------------------------------------------------------------- /test/spin.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidthesloth92/three-finger-tap-js/HEAD/test/spin.gif -------------------------------------------------------------------------------- /logo/logos.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidthesloth92/three-finger-tap-js/HEAD/logo/logos.sketch -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | notifications: 7 | email: false 8 | node_js: 9 | - '4' 10 | before_install: 11 | - npm i -g npm@^2.0.0 12 | before_script: 13 | - npm prune 14 | after_success: 15 | - npm run semantic-release 16 | branches: 17 | except: 18 | - /^v\d+\.\d+\.\d+$/ 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Dinesh Balaji 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/css/threeFingerTap.min.css: -------------------------------------------------------------------------------- 1 | .tft_iframe_wrapper{position:fixed;width:70%;height:45%;box-shadow:0 0 5px 0 #4b4b4b;visibility:hidden;transition:-webkit-transform .1s linear;transition:transform .1s linear;transition:transform .1s linear,-webkit-transform .1s linear;background:center no-repeat #4b4b4b;background-size:50px;-webkit-transform:scale(0,0);transform:scale(0,0);overflow:auto;-ms-overflow-style:-ms-autohiding-scrollbar}.tft_iframe_wrapper.show{display:block;visibility:visible;-webkit-transform:scale(1,1);transform:scale(1,1)}.tft_iframe_wrapper iframe{width:100%;height:100%;border-style:none}.tft_iframe_wrapper .loader{position:absolute;width:30px;height:30px;left:50%;top:50%;border-radius:50%;border:5px solid transparent;border-top-color:rgba(255,255,255,.4);-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);z-index:-1;-webkit-animation:spin 2s linear infinite;animation:spin 2s linear infinite}.noscroll{overflow:hidden;-webkit-overflow-scrolling:touch!important;cursor:pointer}@-webkit-keyframes spin{from{-webkit-transform:translate(-50%,-50%) rotate(0);transform:translate(-50%,-50%) rotate(0)}to{-webkit-transform:translate(-50%,-50%) rotate(360deg);transform:translate(-50%,-50%) rotate(360deg)}}@keyframes spin{from{-webkit-transform:translate(-50%,-50%) rotate(0);transform:translate(-50%,-50%) rotate(0)}to{-webkit-transform:translate(-50%,-50%) rotate(360deg);transform:translate(-50%,-50%) rotate(360deg)}} -------------------------------------------------------------------------------- /tests/init.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const threeFingerTap = require('../dist/js/threeFingerTap.js'); 4 | 5 | let name = 'three-finger-tap'; 6 | let hoverTimeout = 1000; 7 | 8 | describe("test initialization", () => { 9 | 10 | test("test name initialization and hover default" , () => { 11 | threeFingerTap.init({ 12 | name : name, 13 | hoverTimeout : hoverTimeout 14 | }); 15 | 16 | expect(document.body.getElementsByClassName('tft_iframe_wrapper')).toHaveLength(1); 17 | expect(threeFingerTap.getName()).toBe(name); 18 | expect(threeFingerTap.getHoverTimeout()).toEqual(hoverTimeout); 19 | }); 20 | 21 | test("test custom hoverTimeout initialization" , () => { 22 | threeFingerTap.init({ 23 | name : name, 24 | hoverTimeout : 2000 25 | }); 26 | 27 | expect(document.body.getElementsByClassName('tft_iframe_wrapper')).toHaveLength(1); 28 | expect(threeFingerTap.getHoverTimeout()).toEqual(2000); 29 | }); 30 | 31 | test("test default customLoadingBackground" , () => { 32 | threeFingerTap.init({ 33 | name : name, 34 | hoverTimeout : hoverTimeout 35 | }); 36 | 37 | expect(document.body.getElementsByClassName('tft_iframe_wrapper')).toHaveLength(1); 38 | expect(document.body.querySelector('.tft_iframe_wrapper').querySelector('.loader')).toBeDefined(); 39 | }); 40 | 41 | afterEach(() => { 42 | threeFingerTap.destroy(); 43 | }); 44 | 45 | }); -------------------------------------------------------------------------------- /dist/css/threeFingerTap.css: -------------------------------------------------------------------------------- 1 | .tft_iframe_wrapper { 2 | position: fixed; 3 | width: 70%; 4 | height: 45%; 5 | box-shadow: 0px 0px 5px 0px #4b4b4b; 6 | visibility: hidden; 7 | transition: -webkit-transform .1s linear; 8 | transition: transform .1s linear; 9 | transition: transform .1s linear, -webkit-transform .1s linear; 10 | background: #4b4b4b; 11 | background-repeat: no-repeat; 12 | background-size: 50px; 13 | background-position: center; 14 | -webkit-transform: scale(0, 0); 15 | transform: scale(0, 0); 16 | overflow: auto; 17 | -ms-overflow-style: -ms-autohiding-scrollbar; } 18 | .tft_iframe_wrapper.show { 19 | display: block; 20 | visibility: visible; 21 | -webkit-transform: scale(1, 1); 22 | transform: scale(1, 1); } 23 | .tft_iframe_wrapper iframe { 24 | width: 100%; 25 | height: 100%; 26 | border-style: none; } 27 | .tft_iframe_wrapper .loader { 28 | position: absolute; 29 | width: 30px; 30 | height: 30px; 31 | left: 50%; 32 | top: 50%; 33 | border-radius: 50%; 34 | border: 5px solid transparent; 35 | border-top-color: rgba(255, 255, 255, 0.4); 36 | -webkit-transform: translate(-50%, -50%); 37 | transform: translate(-50%, -50%); 38 | z-index: -1; 39 | -webkit-animation: spin 2s linear infinite; 40 | animation: spin 2s linear infinite; } 41 | 42 | .noscroll { 43 | overflow: hidden; 44 | -webkit-overflow-scrolling: touch !important; 45 | cursor: pointer; } 46 | 47 | @-webkit-keyframes spin { 48 | from { 49 | -webkit-transform: translate(-50%, -50%) rotate(0deg); 50 | transform: translate(-50%, -50%) rotate(0deg); } 51 | to { 52 | -webkit-transform: translate(-50%, -50%) rotate(360deg); 53 | transform: translate(-50%, -50%) rotate(360deg); } } 54 | 55 | @keyframes spin { 56 | from { 57 | -webkit-transform: translate(-50%, -50%) rotate(0deg); 58 | transform: translate(-50%, -50%) rotate(0deg); } 59 | to { 60 | -webkit-transform: translate(-50%, -50%) rotate(360deg); 61 | transform: translate(-50%, -50%) rotate(360deg); } } 62 | -------------------------------------------------------------------------------- /src/scss/threeFingerTap.scss: -------------------------------------------------------------------------------- 1 | .tft_iframe_wrapper { 2 | position: fixed; 3 | width: 70%; 4 | height: 45%; 5 | box-shadow: 0px 0px 5px 0px #4b4b4b; 6 | visibility: hidden; 7 | transition: transform .1s linear; 8 | background: #4b4b4b; 9 | background-repeat: no-repeat; 10 | background-size: 50px; 11 | background-position: center; 12 | transform: scale(0, 0); 13 | overflow: auto; 14 | // To remove scrollbar from appearing in IE Edge 15 | -ms-overflow-style: -ms-autohiding-scrollbar; 16 | 17 | &.show { 18 | display: block; 19 | visibility: visible; 20 | transform: scale(1, 1); 21 | } 22 | // &:before { 23 | // content: ''; 24 | // position: absolute; 25 | // top: 0; 26 | // left: 50%; 27 | // width: 0px; 28 | // height: 0px; 29 | // border-left: 10px solid transparent; 30 | // border-right: 10px solid transparent; 31 | // border-bottom: 10px solid #4a4a4a; 32 | // transform: translate(-50%, -100%); 33 | // } 34 | iframe { 35 | width: 100%; 36 | height: 100%; 37 | border-style: none; 38 | } 39 | .loader { 40 | position: absolute; 41 | width: 30px; 42 | height: 30px; 43 | left: 50%; 44 | top: 50%; 45 | border-radius: 50%; 46 | border: 5px solid rgba(0, 0, 0, 0); 47 | border-top-color: rgba(255, 255, 255, 0.4); 48 | transform: translate(-50%, -50%); 49 | z-index: -1; 50 | animation: spin 2s linear infinite; 51 | } 52 | } 53 | 54 | .noscroll { 55 | overflow: hidden; 56 | 57 | //Fixed content not scrolling scrolls the background 58 | // https://stackoverflow.com/questions/29001977/safari-in-ios8-is-scrolling-screen-when-fixed-elements-get-focus 59 | -webkit-overflow-scrolling: touch !important; 60 | //Click not working in iOS safari 61 | // https://stackoverflow.com/questions/14795944/jquery-click-events-not-working-in-ios 62 | cursor: pointer; 63 | } 64 | 65 | @keyframes spin { 66 | from { transform: translate(-50%, -50%) rotate(0deg); } 67 | to { transform: translate(-50%, -50%) rotate(360deg); } 68 | } 69 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Three Finger Tap JS 9 | 10 | 11 | 12 | 13 | 35 | 36 | 37 | 38 |
39 |
One
40 |
Two
41 |
Three
42 |
Four
43 |
Five
44 |
Six
45 |
Seven
46 |
Eight
47 |
Nine
48 |
Ten
49 |
Eleven
50 |
51 | 52 | 53 | 54 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-finger-tap-js", 3 | "description": "A small library that attempts to mimic the three finger tap behaviour exhibited when you do a three finder tap on an URL in Safari.", 4 | "main": "src/index.js", 5 | "scripts": { 6 | "test": "jest", 7 | "lint": "jshint src/js/*", 8 | "prejs": "rm -rf dist/js && mkdir dist/js/", 9 | "js": "babel --presets babel-preset-es2015 --out-file dist/js/threeFingerTap.js src/js", 10 | "minify:js": "uglifyjs --compress --mangle -o dist/js/threeFingerTap.min.js -- dist/js/threeFingerTap.js", 11 | "scss": "node-sass src/scss/threeFingerTap.scss dist/css/threeFingerTap.css", 12 | "postcss": "postcss --use autoprefixer -o dist/css/threeFingerTap.css dist/css/threeFingerTap.css", 13 | "minify:css": "cleancss dist/css/threeFingerTap.css -o dist/css/threeFingerTap.min.css", 14 | "combine": "node-sass src/scss/main.scss | postcss --use autoprefixer | cleancss -o dist/threeFingerTap.min.css", 15 | "build:js:dev": "npm run lint && npm run js", 16 | "build:js:prod": "npm run lint && npm run js && npm run minify:js", 17 | "build:css:dev": "npm run scss && npm run postcss", 18 | "build:css:prod": "npm run scss && npm run postcss && npm run minify:css", 19 | "build:dev": "npm run build:js:dev && npm run build:css:dev", 20 | "build:prod": "npm run build:js:prod && npm run build:css:prod", 21 | "watch:js": "onchange 'src/**/*.js' -- npm run build:js:dev", 22 | "watch:css": "onchange 'src/**/*.scss' -- npm run build:css:dev", 23 | "watch": "parallelshell 'npm run watch:js' 'npm run watch:css'", 24 | "start": "parallelshell 'npm run watch' 'live-server --open=test'", 25 | "commit": "git-cz", 26 | "semantic-release": "semantic-release pre && npm publish && semantic-release post" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/sidthesloth92/three-finger-tap-js.git" 31 | }, 32 | "keywords": [ 33 | "safari", 34 | "three", 35 | "finger", 36 | "tap", 37 | "url", 38 | "preview" 39 | ], 40 | "author": "Dinesh Balaji (https://dbwriteups.wordpress.com/)", 41 | "license": "MIT", 42 | "bugs": { 43 | "url": "https://github.com/sidthesloth92/three-finger-tap-js/issues" 44 | }, 45 | "homepage": "https://github.com/sidthesloth92/three-finger-tap-js#readme", 46 | "devDependencies": { 47 | "autoprefixer": "6.3.7", 48 | "babel-cli": "6.10.1", 49 | "babel-preset-es2015": "6.9.0", 50 | "clean-css": "3.4.18", 51 | "commitizen": "^2.9.6", 52 | "cz-conventional-changelog": "^2.0.0", 53 | "jest": "^20.0.4", 54 | "jshint": "2.9.2", 55 | "live-server": "1.0.0", 56 | "node-sass": "3.8.0", 57 | "onchange": "2.5.0", 58 | "parallelshell": "2.0.0", 59 | "postcss-cli": "2.5.2", 60 | "semantic-release": "^4.3.5", 61 | "uglify-js": "2.6.4" 62 | }, 63 | "jshintConfig": { 64 | "esversion": 6 65 | }, 66 | "config": { 67 | "commitizen": { 68 | "path": "./node_modules/cz-conventional-changelog" 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /dist/js/threeFingerTap.min.js: -------------------------------------------------------------------------------- 1 | "use strict";!function(){function e(e){var t=e.name,o=e.hoverTimeout,n=e.customLoadingBackground;if(Q)throw new Error("Library already initialized");Q=!0,C=l(),i(t),a(o),u(n),m(),g(),W=!0}function t(){S.removeChild(A),window.removeEventListener("mousemove",w),window.removeEventListener("click",h),S.removeEventListener("click",y),S=A=D=T=C=I=B=void 0,v(),Q=!1,W=!1}function o(){W=!0}function n(){W=!1}function i(e){if(!e)throw new Error("Name not specified");T=e}function r(){return T}function a(e){if(C)B=500,console.log("Touch device. hoverTimeout value ignored.");else{if(isNaN(e))throw new Error("hoverTimeout should have a numerical value");B=e}}function d(){return B}function u(e){var t=document.createElement("div");if(t.style.backgroundImage=e,t.style.backgroundImage&&!e)throw new Error("Invalid value for customLoadingBackground. Must be a possible value for CSS backgroundImage property");I=e,f()}function c(){return I}function s(){return C}function l(){return window.hasOwnProperty("ontouchstart")}function v(){x=void 0,H=!1,N=0,clearTimeout(E)}function m(){var e=document.createDocumentFragment();S=document.querySelector("body"),A=document.createElement("div"),A.classList.add("tft_iframe_wrapper"),D=document.createElement("iframe"),A.appendChild(D),e.appendChild(A),S.appendChild(e),f()}function f(){if(A)if(I){var e=A.querySelector(".loader");e&&e.remove(),A.style.backgroundImage=I}else{A.style.backgroundImage="";var t=document.createElement("div");t.classList.add("loader"),A.appendChild(t)}}function g(){C?(H=!1,window.addEventListener("click",h)):window.addEventListener("mousemove",w),S.addEventListener("click",y)}function w(e){W&&e.target.classList.contains(T)?x||(x=e.target,E=setTimeout(function(){x&&x.classList.contains(T)&&p()},B)):v()}function h(e){e.target.classList.contains(T)?(e.target!==x&&(N=0,x=e.target,clearTimeout(E)),H?v():(N++,1==N&&(E=setTimeout(function(){x&&(N>=3&&W?(p(),v()):(H=!0,x.click()))},B)),e.preventDefault())):v()}function p(){var e=x.getBoundingClientRect(),t=L(e),o=t.xQuadrant,n=t.yQuadrant,i=b({xQuadrant:o,yQuadrant:n,positionBox:e});i.src=x.getAttribute("href"),k(i)}function y(e){A.classList.remove("show"),A.style.left="",A.style.top="",D.setAttribute("src",""),S.classList.remove("noscroll")}function L(e){var t=e.left,o=e.top;console.log(t,o);var n=window.innerWidth/2-.1*window.innerWidth,i=window.innerWidth/2+.1*window.innerWidth,r=t` (anchor) tags for which you wish to add the hover effect to. 43 | ```html 44 | Link 45 | ``` 46 | 2.Add references to `threeFingerTap.min.js` and `threeFingerTap.min.css` to the HTML page. 47 | ```html 48 | 49 | 50 | 51 | 52 | 53 | ``` 54 | 3.Call the library using the init method and see the magic happen. 55 | ```javascript 56 | threeFingerTap.init({ 57 | name : 'three-finger-tap', 58 | hoverTimeout : 1000 // required only for desktop 59 | }) 60 | ``` 61 | ## API 62 | 63 | ### Methods 64 | 65 | `init` 66 | 67 | Initializes the library and adds it to the window object with the name threeFingerTap. Use this method only once when you 68 | load the page. 69 | 70 | **Parameters** 71 | 72 | An object with name, hoverTimeout and customLoadingBackground as keys. 73 | 74 | | Name | Description | Type | Required | Default | 75 | | :--- | :---------- | :--: | :------: | :-----: | 76 | | name | name of the CSS class name that will be used to identify the links to apply the hover effect on | String | yes | N/A | 77 | | hoverTimeout | the duration for which a user needs to hover over an URL before the preview window appears. The value should be in milliseconds (Desktop only) | Number | yes | Ignored and set to 500 for Mobile | 78 | | customLoadingBackground | By default a loading background is added, you can use this parameter to set a custom image or gif as background. The value should be a valid value for the CSS `background-image` property. The path to the image/gif should be relative to page the effect will be displayed | String | no | N/A | 79 | 80 | **Usage** 81 | 82 | ```javascript 83 | threeFingerTap.init({ 84 | name : 'class-name', 85 | hoverTimeout : 1000, // required only for Desktop 86 | customLoadingBackground : "url('../assets/gifs/spinner.gif')" 87 | }) 88 | ``` 89 | 90 | All the three options can be specified either at the time the `init` function is called by passing them as parameters, or 91 | you can modify them any time after that using their corresponding getters and setters. All the three options are dynamic and changes 92 | reflect as soon as they are modified. 93 | 94 |
95 | 96 | `disable` 97 | 98 | Disables the hover effect added by the library 99 | 100 | **Usage** 101 | 102 | ```javascript 103 | threeFingerTap.disable(); 104 | ``` 105 | 106 |
107 | 108 | `enable` 109 | 110 | Enables the hover effect added by the library 111 | 112 | **Usage** 113 | 114 | ```javascript 115 | threeFingerTap.enable(); 116 | ``` 117 | 118 | ### Getter and Setters 119 | 120 | **Usage** 121 | 122 | | Name | Description | Parameters | Required | Parameter Type | 123 | | :--- | :---------- | :--: | :------: | :-----: | 124 | | getName | Returns the current value of the `name` option | N/A | N/A | N/A | 125 | | setName | Updates the `name` value to the value passed as parameter | String | yes | A CSS valid class name | 126 | | getHoverTimeout | Returns the current value of the `hoverTimeout` option | N/A | N/A | N/A | 127 | | setHoverTimeout | Updates the `hoverTimeout` to the value passed as parameter for desktop. In case of mobile, ignore the parameter passed. | Number | yes | milliseconds | 128 | | getCustomLoadingBackground | Returns the current value of the `customLoadingBackground` option | N/A | N/A | N/A | 129 | | setCustomLoadingBackground | Sets the loading effect to the passed GIF/image | String | yes | A valid value for the CSS `background-image` property | 130 | 131 |
132 | 133 | `destroy` 134 | 135 | Removes the DOM nodes and their respective event listeners. Also resets the library to the initial state. 136 | 137 | **Usage** 138 | 139 | ```javascript 140 | threeFingerTap.destroy(); 141 | ``` 142 | 143 | # Browsers support 144 | 145 | | [IE / Edge](http://godban.github.io/browsers-support-badges/)
IE / Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | [Opera](http://godban.github.io/browsers-support-badges/)
Opera | 146 | | --------- | --------- | --------- | --------- | --------- | 147 | 148 | # Contact 149 | If you have any issues report them at [Issues](https://github.com/sidthesloth92/three-finger-tap-js/issues) 150 | 151 | # Source 152 | [Github](https://github.com/sidthesloth92/three-finger-tap-js) -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | 3 | // Private variables 4 | let _currentNode; // specifies the current DOM element hovered or tapped 5 | let _timeout; // specifies the hover timeout or tap timeout 6 | let _name; // specifies the CSS class name 7 | let _initialized; // specifies whether the library is initialized or not 8 | let _isMobile; // indicates a mobile device or nont 9 | 10 | // API variables 11 | let _hoverTimeout; // user specified value for the hover timeout, set to 500 if mobile 12 | let _customLoadingBackground; // user specified backgroundImage CSS value 13 | let _enable = true; // user specified value indicating whether the library is currently active 14 | 15 | // Private variables 16 | let _count = 0; // number of times the user has tapped the link within the timeout 17 | let _openLink = false; 18 | 19 | // DOM nodes 20 | let _body; 21 | let _iframeWrapper; 22 | let _iframe; 23 | 24 | // API Methods 25 | function init({ name, hoverTimeout, customLoadingBackground}) { 26 | if(!_initialized) { 27 | _initialized = true; 28 | } 29 | else { 30 | throw new Error("Library already initialized"); 31 | } 32 | _isMobile = _isTouchDevice(); 33 | setName(name); 34 | setHoverTimeout(hoverTimeout); 35 | setCustomLoadingBackground(customLoadingBackground); 36 | _constructDOM(); 37 | _addEventListeners(); 38 | _enable = true; 39 | } 40 | 41 | function destroy() { 42 | _body.removeChild(_iframeWrapper); 43 | 44 | window.removeEventListener('mousemove', _browserFunctionality); 45 | window.removeEventListener('click', _mobileFunctionality); 46 | 47 | _body.removeEventListener('click', _hidePreviewWindow); 48 | 49 | _body = _iframeWrapper = _iframe = _name = _isMobile = _customLoadingBackground = _hoverTimeout = undefined; 50 | _reset(); 51 | 52 | _initialized = false; 53 | _enable = false; 54 | } 55 | 56 | function enable() { 57 | _enable = true; 58 | } 59 | 60 | function disable() { 61 | _enable = false; 62 | } 63 | 64 | function setName(name) { 65 | if (!name) { 66 | throw new Error("Name not specified"); 67 | } 68 | _name = name; 69 | } 70 | 71 | function getName() { 72 | return _name; 73 | } 74 | 75 | function setHoverTimeout(hoverTimeout) { 76 | if(!_isMobile) { 77 | if (isNaN(hoverTimeout)) { 78 | throw new Error("hoverTimeout should have a numerical value"); 79 | } 80 | _hoverTimeout = hoverTimeout; 81 | } 82 | else { 83 | _hoverTimeout = 500; 84 | console.log("Touch device. hoverTimeout value ignored."); 85 | } 86 | } 87 | 88 | function getHoverTimeout() { 89 | return _hoverTimeout; 90 | } 91 | 92 | function setCustomLoadingBackground(customLoadingBackground) { 93 | let tempDiv = document.createElement('div'); 94 | tempDiv.style.backgroundImage = customLoadingBackground; 95 | 96 | if (tempDiv.style.backgroundImage && !customLoadingBackground) { 97 | throw new Error("Invalid value for customLoadingBackground. Must be a possible value for CSS backgroundImage property"); 98 | } 99 | _customLoadingBackground = customLoadingBackground; 100 | _updateCustomLoadingBackground(); 101 | } 102 | 103 | function getCustomLoadingBackground() { 104 | return _customLoadingBackground; 105 | } 106 | 107 | function getIsMobileDevice() { 108 | return _isMobile; 109 | } 110 | 111 | // Private Methods 112 | function _isTouchDevice() { 113 | return window.hasOwnProperty('ontouchstart'); 114 | } 115 | 116 | function _reset() { 117 | _currentNode = undefined; 118 | _openLink = false; 119 | _count = 0; 120 | clearTimeout(_timeout); 121 | } 122 | 123 | function _constructDOM() { 124 | let fragment = document.createDocumentFragment(); 125 | 126 | _body = document.querySelector('body'); 127 | _iframeWrapper = document.createElement('div'); 128 | _iframeWrapper.classList.add('tft_iframe_wrapper'); 129 | 130 | _iframe = document.createElement('iframe'); 131 | _iframeWrapper.appendChild(_iframe); 132 | 133 | fragment.appendChild(_iframeWrapper); 134 | _body.appendChild(fragment); 135 | 136 | _updateCustomLoadingBackground(); 137 | } 138 | 139 | function _updateCustomLoadingBackground() { 140 | if (_iframeWrapper) { 141 | if (!_customLoadingBackground) { 142 | _iframeWrapper.style.backgroundImage = ""; 143 | 144 | let loader = document.createElement('div'); 145 | loader.classList.add('loader'); 146 | _iframeWrapper.appendChild(loader); 147 | } 148 | else { 149 | let loader = _iframeWrapper.querySelector('.loader'); 150 | if(loader) { 151 | loader.remove(); 152 | } 153 | 154 | _iframeWrapper.style.backgroundImage = _customLoadingBackground; 155 | } 156 | } 157 | } 158 | 159 | function _addEventListeners() { 160 | if (!_isMobile) { 161 | window.addEventListener('mousemove', _browserFunctionality); 162 | } else { 163 | _openLink = false; 164 | window.addEventListener('click', _mobileFunctionality); 165 | } 166 | _body.addEventListener('click', _hidePreviewWindow); 167 | } 168 | function _browserFunctionality(event) { 169 | if (_enable && event.target.classList.contains(_name)) { 170 | if (!_currentNode) { 171 | _currentNode = event.target; 172 | _timeout = setTimeout(function () { 173 | if (_currentNode && _currentNode.classList.contains(_name)) { 174 | _showPreviewWindow(); 175 | } 176 | }, _hoverTimeout); 177 | } 178 | } else { 179 | _reset(); 180 | } 181 | } 182 | function _mobileFunctionality(event) { 183 | if (event.target.classList.contains(_name)) { 184 | if (event.target !== _currentNode) { 185 | _count = 0; 186 | _currentNode = event.target; 187 | clearTimeout(_timeout); 188 | } 189 | if (!_openLink) { 190 | _count++; 191 | 192 | if (_count == 1) { 193 | _timeout = setTimeout(function () { 194 | if (_currentNode) { 195 | if (_count >= 3 && _enable) { 196 | _showPreviewWindow(); 197 | _reset(); 198 | } else { 199 | _openLink = true; 200 | _currentNode.click(); 201 | } 202 | } 203 | }, _hoverTimeout); 204 | } 205 | event.preventDefault(); 206 | } else { 207 | _reset(); 208 | } 209 | } else { 210 | _reset(); 211 | } 212 | } 213 | 214 | function _showPreviewWindow() { 215 | let positionBox = _currentNode.getBoundingClientRect(); 216 | let { xQuadrant, yQuadrant } = _findQuadrant(positionBox); 217 | 218 | let previewWindowData = _findPreviewWindowPosition({ xQuadrant, yQuadrant, positionBox }); 219 | previewWindowData.src = _currentNode.getAttribute('href'); 220 | _updatePreviewWindow(previewWindowData); 221 | } 222 | 223 | function _hidePreviewWindow(event) { 224 | _iframeWrapper.classList.remove('show'); 225 | _iframeWrapper.style.left = ""; 226 | _iframeWrapper.style.top = ""; 227 | _iframe.setAttribute('src', ''); 228 | _body.classList.remove('noscroll'); 229 | } 230 | 231 | function _findQuadrant(positionBox) { 232 | let { left: x, top: y } = positionBox; 233 | console.log(x, y); 234 | 235 | // var xQuadrant = (x < (window.innerWidth / 3)) ? 0 : (x < (window.innerWidth / 3 * 2)) ? 1 : 2; 236 | // var yQuadrant = (y < (window.innerHeight / 3)) ? 0 : (y < (window.innerHeight / 3 * 2)) ? 1 : 2; 237 | let xPointOne = (window.innerWidth / 2) - (0.1 * window.innerWidth); 238 | let xPointTwo = (window.innerWidth / 2) + (0.1 * window.innerWidth); 239 | let xQuadrant = (x < xPointOne) ? 0 : (x < xPointTwo) ? 1 : 2; 240 | let yQuadrant = (y < (window.innerHeight / 2)) ? 0 : 1; 241 | console.log(`xQuadrant: ${xQuadrant}, yQuadrant : ${yQuadrant}`); 242 | 243 | return { 244 | xQuadrant, 245 | yQuadrant 246 | }; 247 | } 248 | 249 | function _findPreviewWindowPosition({ xQuadrant, yQuadrant, positionBox }) { 250 | 251 | let top, bottom, left, right; 252 | 253 | if (xQuadrant === 0) { 254 | left = positionBox.left + 'px'; 255 | right = ""; 256 | } 257 | else if (xQuadrant === 1) { 258 | left = (positionBox.left - ((window.innerWidth * 0.7) / 2) + (positionBox.width / 2)) + 'px'; 259 | right = ""; 260 | } 261 | else if (xQuadrant === 2) { 262 | left = ""; 263 | right = (window.innerWidth - positionBox.right) + 'px'; 264 | } 265 | 266 | if (!yQuadrant) { 267 | top = positionBox.top + positionBox.height + 10 + 'px'; 268 | bottom = ""; 269 | } 270 | else { 271 | top = ""; 272 | bottom = (window.innerHeight - positionBox.top + 10) + 'px'; 273 | } 274 | // var left = (0.3 * window.innerWidth) - (((0.3 * window.innerWidth) / 2) * xQuadrant); 275 | // var top = (0.3 * window.innerHeight) - (((0.3 * window.innerHeight) / 2) * yQuadrant); 276 | 277 | console.log(`top: ${top} bottom : ${bottom}`); 278 | console.log(`left : ${left} right : ${right}`); 279 | 280 | return { 281 | top, 282 | right, 283 | bottom, 284 | left 285 | }; 286 | } 287 | 288 | function _updatePreviewWindow({ top, bottom, left, right, src }) { 289 | _body.classList.add('noscroll'); 290 | _iframeWrapper.classList.add('show'); 291 | 292 | _iframeWrapper.style.top = top; 293 | _iframeWrapper.style.bottom = bottom; 294 | 295 | _iframeWrapper.style.left = left; 296 | _iframeWrapper.style.right = right; 297 | 298 | _iframe.setAttribute('src', src); 299 | } 300 | 301 | let threeFingerTap = { 302 | init, 303 | destroy, 304 | enable, 305 | disable, 306 | setName, 307 | getName, 308 | getHoverTimeout, 309 | setHoverTimeout, 310 | getCustomLoadingBackground, 311 | setCustomLoadingBackground, 312 | getIsMobileDevice 313 | }; 314 | 315 | if(typeof module !== 'undefined' && typeof module.exports !== 'undefined') { 316 | module.exports = threeFingerTap; 317 | } 318 | else { 319 | window.threeFingerTap = threeFingerTap; 320 | } 321 | })(); 322 | 323 | 324 | -------------------------------------------------------------------------------- /dist/js/threeFingerTap.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function () { 4 | 5 | // Private variables 6 | var _currentNode = void 0; // specifies the current DOM element hovered or tapped 7 | var _timeout = void 0; // specifies the hover timeout or tap timeout 8 | var _name = void 0; // specifies the CSS class name 9 | var _initialized = void 0; // specifies whether the library is initialized or not 10 | var _isMobile = void 0; // indicates a mobile device or nont 11 | 12 | // API variables 13 | var _hoverTimeout = void 0; // user specified value for the hover timeout, set to 500 if mobile 14 | var _customLoadingBackground = void 0; // user specified backgroundImage CSS value 15 | var _enable = true; // user specified value indicating whether the library is currently active 16 | 17 | // Private variables 18 | var _count = 0; // number of times the user has tapped the link within the timeout 19 | var _openLink = false; 20 | 21 | // DOM nodes 22 | var _body = void 0; 23 | var _iframeWrapper = void 0; 24 | var _iframe = void 0; 25 | 26 | // API Methods 27 | function init(_ref) { 28 | var name = _ref.name, 29 | hoverTimeout = _ref.hoverTimeout, 30 | customLoadingBackground = _ref.customLoadingBackground; 31 | 32 | if (!_initialized) { 33 | _initialized = true; 34 | } else { 35 | throw new Error("Library already initialized"); 36 | } 37 | _isMobile = _isTouchDevice(); 38 | setName(name); 39 | setHoverTimeout(hoverTimeout); 40 | setCustomLoadingBackground(customLoadingBackground); 41 | _constructDOM(); 42 | _addEventListeners(); 43 | _enable = true; 44 | } 45 | 46 | function destroy() { 47 | _body.removeChild(_iframeWrapper); 48 | 49 | window.removeEventListener('mousemove', _browserFunctionality); 50 | window.removeEventListener('click', _mobileFunctionality); 51 | 52 | _body.removeEventListener('click', _hidePreviewWindow); 53 | 54 | _body = _iframeWrapper = _iframe = _name = _isMobile = _customLoadingBackground = _hoverTimeout = undefined; 55 | _reset(); 56 | 57 | _initialized = false; 58 | _enable = false; 59 | } 60 | 61 | function enable() { 62 | _enable = true; 63 | } 64 | 65 | function disable() { 66 | _enable = false; 67 | } 68 | 69 | function setName(name) { 70 | if (!name) { 71 | throw new Error("Name not specified"); 72 | } 73 | _name = name; 74 | } 75 | 76 | function getName() { 77 | return _name; 78 | } 79 | 80 | function setHoverTimeout(hoverTimeout) { 81 | if (!_isMobile) { 82 | if (isNaN(hoverTimeout)) { 83 | throw new Error("hoverTimeout should have a numerical value"); 84 | } 85 | _hoverTimeout = hoverTimeout; 86 | } else { 87 | _hoverTimeout = 500; 88 | console.log("Touch device. hoverTimeout value ignored."); 89 | } 90 | } 91 | 92 | function getHoverTimeout() { 93 | return _hoverTimeout; 94 | } 95 | 96 | function setCustomLoadingBackground(customLoadingBackground) { 97 | var tempDiv = document.createElement('div'); 98 | tempDiv.style.backgroundImage = customLoadingBackground; 99 | 100 | if (tempDiv.style.backgroundImage && !customLoadingBackground) { 101 | throw new Error("Invalid value for customLoadingBackground. Must be a possible value for CSS backgroundImage property"); 102 | } 103 | _customLoadingBackground = customLoadingBackground; 104 | _updateCustomLoadingBackground(); 105 | } 106 | 107 | function getCustomLoadingBackground() { 108 | return _customLoadingBackground; 109 | } 110 | 111 | function getIsMobileDevice() { 112 | return _isMobile; 113 | } 114 | 115 | // Private Methods 116 | function _isTouchDevice() { 117 | return window.hasOwnProperty('ontouchstart'); 118 | } 119 | 120 | function _reset() { 121 | _currentNode = undefined; 122 | _openLink = false; 123 | _count = 0; 124 | clearTimeout(_timeout); 125 | } 126 | 127 | function _constructDOM() { 128 | var fragment = document.createDocumentFragment(); 129 | 130 | _body = document.querySelector('body'); 131 | _iframeWrapper = document.createElement('div'); 132 | _iframeWrapper.classList.add('tft_iframe_wrapper'); 133 | 134 | _iframe = document.createElement('iframe'); 135 | _iframeWrapper.appendChild(_iframe); 136 | 137 | fragment.appendChild(_iframeWrapper); 138 | _body.appendChild(fragment); 139 | 140 | _updateCustomLoadingBackground(); 141 | } 142 | 143 | function _updateCustomLoadingBackground() { 144 | if (_iframeWrapper) { 145 | if (!_customLoadingBackground) { 146 | _iframeWrapper.style.backgroundImage = ""; 147 | 148 | var loader = document.createElement('div'); 149 | loader.classList.add('loader'); 150 | _iframeWrapper.appendChild(loader); 151 | } else { 152 | var _loader = _iframeWrapper.querySelector('.loader'); 153 | if (_loader) { 154 | _loader.remove(); 155 | } 156 | 157 | _iframeWrapper.style.backgroundImage = _customLoadingBackground; 158 | } 159 | } 160 | } 161 | 162 | function _addEventListeners() { 163 | if (!_isMobile) { 164 | window.addEventListener('mousemove', _browserFunctionality); 165 | } else { 166 | _openLink = false; 167 | window.addEventListener('click', _mobileFunctionality); 168 | } 169 | _body.addEventListener('click', _hidePreviewWindow); 170 | } 171 | function _browserFunctionality(event) { 172 | if (_enable && event.target.classList.contains(_name)) { 173 | if (!_currentNode) { 174 | _currentNode = event.target; 175 | _timeout = setTimeout(function () { 176 | if (_currentNode && _currentNode.classList.contains(_name)) { 177 | _showPreviewWindow(); 178 | } 179 | }, _hoverTimeout); 180 | } 181 | } else { 182 | _reset(); 183 | } 184 | } 185 | function _mobileFunctionality(event) { 186 | if (event.target.classList.contains(_name)) { 187 | if (event.target !== _currentNode) { 188 | _count = 0; 189 | _currentNode = event.target; 190 | clearTimeout(_timeout); 191 | } 192 | if (!_openLink) { 193 | _count++; 194 | 195 | if (_count == 1) { 196 | _timeout = setTimeout(function () { 197 | if (_currentNode) { 198 | if (_count >= 3 && _enable) { 199 | _showPreviewWindow(); 200 | _reset(); 201 | } else { 202 | _openLink = true; 203 | _currentNode.click(); 204 | } 205 | } 206 | }, _hoverTimeout); 207 | } 208 | event.preventDefault(); 209 | } else { 210 | _reset(); 211 | } 212 | } else { 213 | _reset(); 214 | } 215 | } 216 | 217 | function _showPreviewWindow() { 218 | var positionBox = _currentNode.getBoundingClientRect(); 219 | 220 | var _findQuadrant2 = _findQuadrant(positionBox), 221 | xQuadrant = _findQuadrant2.xQuadrant, 222 | yQuadrant = _findQuadrant2.yQuadrant; 223 | 224 | var previewWindowData = _findPreviewWindowPosition({ xQuadrant: xQuadrant, yQuadrant: yQuadrant, positionBox: positionBox }); 225 | previewWindowData.src = _currentNode.getAttribute('href'); 226 | _updatePreviewWindow(previewWindowData); 227 | } 228 | 229 | function _hidePreviewWindow(event) { 230 | _iframeWrapper.classList.remove('show'); 231 | _iframeWrapper.style.left = ""; 232 | _iframeWrapper.style.top = ""; 233 | _iframe.setAttribute('src', ''); 234 | _body.classList.remove('noscroll'); 235 | } 236 | 237 | function _findQuadrant(positionBox) { 238 | var x = positionBox.left, 239 | y = positionBox.top; 240 | 241 | console.log(x, y); 242 | 243 | // var xQuadrant = (x < (window.innerWidth / 3)) ? 0 : (x < (window.innerWidth / 3 * 2)) ? 1 : 2; 244 | // var yQuadrant = (y < (window.innerHeight / 3)) ? 0 : (y < (window.innerHeight / 3 * 2)) ? 1 : 2; 245 | var xPointOne = window.innerWidth / 2 - 0.1 * window.innerWidth; 246 | var xPointTwo = window.innerWidth / 2 + 0.1 * window.innerWidth; 247 | var xQuadrant = x < xPointOne ? 0 : x < xPointTwo ? 1 : 2; 248 | var yQuadrant = y < window.innerHeight / 2 ? 0 : 1; 249 | console.log('xQuadrant: ' + xQuadrant + ', yQuadrant : ' + yQuadrant); 250 | 251 | return { 252 | xQuadrant: xQuadrant, 253 | yQuadrant: yQuadrant 254 | }; 255 | } 256 | 257 | function _findPreviewWindowPosition(_ref2) { 258 | var xQuadrant = _ref2.xQuadrant, 259 | yQuadrant = _ref2.yQuadrant, 260 | positionBox = _ref2.positionBox; 261 | 262 | 263 | var top = void 0, 264 | bottom = void 0, 265 | left = void 0, 266 | right = void 0; 267 | 268 | if (xQuadrant === 0) { 269 | left = positionBox.left + 'px'; 270 | right = ""; 271 | } else if (xQuadrant === 1) { 272 | left = positionBox.left - window.innerWidth * 0.7 / 2 + positionBox.width / 2 + 'px'; 273 | right = ""; 274 | } else if (xQuadrant === 2) { 275 | left = ""; 276 | right = window.innerWidth - positionBox.right + 'px'; 277 | } 278 | 279 | if (!yQuadrant) { 280 | top = positionBox.top + positionBox.height + 10 + 'px'; 281 | bottom = ""; 282 | } else { 283 | top = ""; 284 | bottom = window.innerHeight - positionBox.top + 10 + 'px'; 285 | } 286 | // var left = (0.3 * window.innerWidth) - (((0.3 * window.innerWidth) / 2) * xQuadrant); 287 | // var top = (0.3 * window.innerHeight) - (((0.3 * window.innerHeight) / 2) * yQuadrant); 288 | 289 | console.log('top: ' + top + ' bottom : ' + bottom); 290 | console.log('left : ' + left + ' right : ' + right); 291 | 292 | return { 293 | top: top, 294 | right: right, 295 | bottom: bottom, 296 | left: left 297 | }; 298 | } 299 | 300 | function _updatePreviewWindow(_ref3) { 301 | var top = _ref3.top, 302 | bottom = _ref3.bottom, 303 | left = _ref3.left, 304 | right = _ref3.right, 305 | src = _ref3.src; 306 | 307 | _body.classList.add('noscroll'); 308 | _iframeWrapper.classList.add('show'); 309 | 310 | _iframeWrapper.style.top = top; 311 | _iframeWrapper.style.bottom = bottom; 312 | 313 | _iframeWrapper.style.left = left; 314 | _iframeWrapper.style.right = right; 315 | 316 | _iframe.setAttribute('src', src); 317 | } 318 | 319 | var threeFingerTap = { 320 | init: init, 321 | destroy: destroy, 322 | enable: enable, 323 | disable: disable, 324 | setName: setName, 325 | getName: getName, 326 | getHoverTimeout: getHoverTimeout, 327 | setHoverTimeout: setHoverTimeout, 328 | getCustomLoadingBackground: getCustomLoadingBackground, 329 | setCustomLoadingBackground: setCustomLoadingBackground, 330 | getIsMobileDevice: getIsMobileDevice 331 | }; 332 | 333 | if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { 334 | module.exports = threeFingerTap; 335 | } else { 336 | window.threeFingerTap = threeFingerTap; 337 | } 338 | })(); 339 | --------------------------------------------------------------------------------