├── LICENSE.md ├── README.md ├── _locales └── en │ └── messages.json ├── docs ├── screenshot-active.png └── screenshot.png ├── icons ├── icon128.png ├── icon16.png ├── icon19.png ├── icon24.png ├── icon32.png ├── icon48.png └── icon96.png ├── manifest.json └── src ├── bg └── background.js ├── inject ├── birdview.css ├── birdview.js └── inject.js ├── options.html └── options.js /LICENSE.md: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2017, Jess Telford 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Birdview 2 | 3 | > Chrome Extension that gives you a glance at a whole web page with an aerial view 4 | 5 | **[Install in Chrome](https://chrome.google.com/webstore/detail/birdview/mkkcfebffmojpmhnnhjiocmhiidjogge)** 6 | 7 | _Based on [Achraf Kassioui](https://twitter.com/achrafkassioui)'s [Birdview.js](http://achrafkassioui.com/birdview)._ 8 | 9 | ![Screenshot](./docs/screenshot.png) 10 | 11 | ![Screenshot Active](./docs/screenshot-active.png) 12 | -------------------------------------------------------------------------------- /_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "l10nTabName": { 3 | "message":"Localization" 4 | ,"description":"name of the localization tab" 5 | } 6 | ,"l10nHeader": { 7 | "message":"It does localization too! (this whole tab is, actually)" 8 | ,"description":"Header text for the localization section" 9 | } 10 | ,"l10nIntro": { 11 | "message":"'L10n' refers to 'Localization' - 'L' an 'n' are obvious, and 10 comes from the number of letters between those two. It is the process/whatever of displaying something in the language of choice. It uses 'I18n', 'Internationalization', which refers to the tools / framework supporting L10n. I.e., something is internationalized if it has I18n support, and can be localized. Something is localized for you if it is in your language / dialect." 12 | ,"description":"introduce the basic idea." 13 | } 14 | ,"l10nProd": { 15 | "message":"You are planning to allow localization, right? You have no idea who will be using your extension! You have no idea who will be translating it! At least support the basics, it's not hard, and having the framework in place will let you transition much more easily later on." 16 | ,"description":"drive the point home. It's good for you." 17 | } 18 | ,"l10nFirstParagraph": { 19 | "message":"When the options page loads, elements decorated with data-l10n will automatically be localized!" 20 | ,"description":"inform that elements will be localized on load" 21 | } 22 | ,"l10nSecondParagraph": { 23 | "message":"If you need more complex localization, you can also define data-l10n-args. This should contain $containerType$ filled with $dataType$, which will be passed into Chrome's i18n API as $functionArgs$. In fact, this paragraph does just that, and wraps the args in mono-space font. Easy!" 24 | ,"description":"introduce the data-l10n-args attribute. End on a lame note." 25 | ,"placeholders": { 26 | "containerType": { 27 | "content":"$1" 28 | ,"example":"'array', 'list', or something similar" 29 | ,"description":"type of the args container" 30 | } 31 | ,"dataType": { 32 | "content":"$2" 33 | ,"example":"string" 34 | ,"description":"type of data in each array index" 35 | } 36 | ,"functionArgs": { 37 | "content":"$3" 38 | ,"example":"arguments" 39 | ,"description":"whatever you call what you pass into a function/method. args, params, etc." 40 | } 41 | } 42 | } 43 | ,"l10nThirdParagraph": { 44 | "message":"Message contents are passed right into innerHTML without processing - include any tags (or even scripts) that you feel like. If you have an input field, the placeholder will be set instead, and buttons will have the value attribute set." 45 | ,"description":"inform that we handle placeholders, buttons, and direct HTML input" 46 | } 47 | ,"l10nButtonsBefore": { 48 | "message":"Different types of buttons are handled as well. <button> elements have their html set:" 49 | } 50 | ,"l10nButton": { 51 | "message":"in a button" 52 | } 53 | ,"l10nButtonsBetween": { 54 | "message":"while <input type='submit'> and <input type='button'> get their 'value' set (note: no HTML):" 55 | } 56 | ,"l10nSubmit": { 57 | "message":"a submit value" 58 | } 59 | ,"l10nButtonsAfter": { 60 | "message":"Awesome, no?" 61 | } 62 | ,"l10nExtras": { 63 | "message":"You can even set data-l10n on things like the <title> tag, which lets you have translatable page titles, or fieldset <legend> tags, or anywhere else - the default Boil.localize() behavior will check every tag in the document, not just the body." 64 | ,"description":"inform about places which may not be obvious, like , etc" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /docs/screenshot-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jesstelford/birdview-chrome-extension/3f21dad9c1d5b4bad90326b110feffb4fdf33b1f/docs/screenshot-active.png -------------------------------------------------------------------------------- /docs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jesstelford/birdview-chrome-extension/3f21dad9c1d5b4bad90326b110feffb4fdf33b1f/docs/screenshot.png -------------------------------------------------------------------------------- /icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jesstelford/birdview-chrome-extension/3f21dad9c1d5b4bad90326b110feffb4fdf33b1f/icons/icon128.png -------------------------------------------------------------------------------- /icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jesstelford/birdview-chrome-extension/3f21dad9c1d5b4bad90326b110feffb4fdf33b1f/icons/icon16.png -------------------------------------------------------------------------------- /icons/icon19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jesstelford/birdview-chrome-extension/3f21dad9c1d5b4bad90326b110feffb4fdf33b1f/icons/icon19.png -------------------------------------------------------------------------------- /icons/icon24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jesstelford/birdview-chrome-extension/3f21dad9c1d5b4bad90326b110feffb4fdf33b1f/icons/icon24.png -------------------------------------------------------------------------------- /icons/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jesstelford/birdview-chrome-extension/3f21dad9c1d5b4bad90326b110feffb4fdf33b1f/icons/icon32.png -------------------------------------------------------------------------------- /icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jesstelford/birdview-chrome-extension/3f21dad9c1d5b4bad90326b110feffb4fdf33b1f/icons/icon48.png -------------------------------------------------------------------------------- /icons/icon96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jesstelford/birdview-chrome-extension/3f21dad9c1d5b4bad90326b110feffb4fdf33b1f/icons/icon96.png -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Birdview", 3 | "version": "1.2.0", 4 | "manifest_version": 2, 5 | "description": "Get a glance at a whole web page with an aerial view", 6 | "homepage_url": "http://achrafkassioui.com/birdview", 7 | "icons": { 8 | "16": "icons/icon16.png", 9 | "19": "icons/icon19.png", 10 | "24": "icons/icon24.png", 11 | "32": "icons/icon32.png", 12 | "48": "icons/icon48.png", 13 | "96": "icons/icon96.png", 14 | "128": "icons/icon128.png" 15 | }, 16 | "default_locale": "en", 17 | "background": { 18 | "scripts": [ 19 | "src/bg/background.js" 20 | ], 21 | "persistent": false 22 | }, 23 | "options_ui": { 24 | "page": "src/options.html", 25 | "chrome_style": true 26 | }, 27 | "browser_action": { 28 | "default_title": "browser action demo", 29 | "default_icon": { 30 | "16": "icons/icon16.png", 31 | "19": "icons/icon19.png", 32 | "24": "icons/icon24.png", 33 | "32": "icons/icon32.png", 34 | "48": "icons/icon48.png", 35 | "96": "icons/icon96.png", 36 | "128": "icons/icon96.png" 37 | } 38 | }, 39 | "permissions": [ 40 | "https://*/*", 41 | "http://*/*", 42 | "storage" 43 | ], 44 | "web_accessible_resources": [ 45 | "src/inject/birdview.js", 46 | "src/inject/birdview.css" 47 | ], 48 | "content_scripts": [ 49 | { 50 | "matches": [ 51 | "https://*/*", 52 | "http://*/*" 53 | ], 54 | "js": [ 55 | "src/inject/inject.js" 56 | ] 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /src/bg/background.js: -------------------------------------------------------------------------------- 1 | chrome.browserAction.onClicked.addListener(function(tab) { 2 | chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { 3 | chrome.tabs.sendMessage(tabs[0].id, {action: 'icon-clicked'}); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /src/inject/birdview.css: -------------------------------------------------------------------------------- 1 | /**************************************************** 2 | * 3 | * Birdview.css 4 | * 1.0 5 | * 6 | * www.achrafkassioui.com/birdview/ 7 | * 8 | * 20 May 2017 9 | * 10 | ****************************************************/ 11 | 12 | #birdview_debug{ 13 | position: fixed; 14 | width: 100%; 15 | height: 100%; 16 | top: 0; 17 | left: 0; 18 | color: #fff; 19 | font-size: 24px; 20 | line-height: 2em; 21 | text-shadow: 0px 0px 5px #000; 22 | text-align: center; 23 | pointer-events: none; 24 | z-index: +9999; 25 | } 26 | 27 | /**************************************************** 28 | * 29 | * Button 30 | * 31 | ****************************************************/ 32 | 33 | #auto_generated_birdview_button{ 34 | position: fixed; 35 | display: block; 36 | width: 60px; 37 | height: 60px; 38 | right: 8px; 39 | top: 8px; 40 | margin: 0; 41 | padding: 0; 42 | text-align: center; 43 | color: #fff; 44 | font-family: 'Futura-PT', Futura, Futura-Medium, "Futura Medium", "Century Gothic", CenturyGothic, "Apple Gothic", AppleGothic, "URW Gothic L", "Avant Garde", sans-serif; 45 | font-style: normal; 46 | font-weight: 500; 47 | font-size: 40px; 48 | line-height: 54px; 49 | border: none; 50 | border-radius: 4px; 51 | background: #01d1fb; 52 | background: rgba(1, 209, 251, 0.8); 53 | cursor: pointer; 54 | z-index: +9998; 55 | } 56 | 57 | #auto_generated_birdview_button:hover, 58 | #auto_generated_birdview_button:active, 59 | #auto_generated_birdview_button:focus{ 60 | background: rgba(1, 209, 251, 1); 61 | outline: none; 62 | } 63 | 64 | #auto_generated_birdview_button::-moz-focus-inner{ 65 | border: 0; 66 | } 67 | 68 | #auto_generated_birdview_button.hidden{ 69 | pointer-events: none; 70 | opacity: 0; 71 | } 72 | 73 | /**************************************************** 74 | * 75 | * Overlay 76 | * 77 | ****************************************************/ 78 | 79 | #auto_generated_birdview_overlay{ 80 | position: fixed; 81 | width: 100%; 82 | height: 100%; 83 | top: 0; 84 | left: 0; 85 | color: #fff; 86 | font-family: 'Futura-PT', Futura, Futura-Medium, "Futura Medium", "Century Gothic", CenturyGothic, "Apple Gothic", AppleGothic, "URW Gothic L", "Avant Garde", sans-serif; 87 | font-style: normal; 88 | font-size: 20px; 89 | background: rgba(0, 0, 0, 0.4); 90 | overflow: hidden; 91 | z-index: +1000; 92 | pointer-events: none; 93 | user-select: none; 94 | -moz-user-select: none; 95 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 96 | transition-property: opacity; 97 | transition-timing-function: ease; 98 | opacity: 0; 99 | } 100 | 101 | #auto_generated_birdview_overlay.show{ 102 | pointer-events: auto; 103 | user-select: auto; 104 | -moz-user-select: auto; 105 | opacity: 1; 106 | touch-action: pinch-zoom; 107 | } 108 | 109 | /* 110 | * 111 | * Overlay title 112 | * 113 | */ 114 | #auto_generated_birdview_overlay h1{ 115 | display: block; 116 | max-width: 33%; 117 | margin: 8px 16px 0px; 118 | text-align: left; 119 | color: #fff; 120 | font-weight: 500; 121 | font-size: 48px; 122 | } 123 | 124 | #auto_generated_birdview_overlay.zooming h1{ 125 | color: #01d1fb; 126 | } 127 | 128 | /* 129 | * 130 | * Breadcrumb navigation 131 | * 132 | */ 133 | #auto_generated_birdview_overlay a{ 134 | display: inline-block; 135 | vertical-align: top; 136 | max-width: 33%; 137 | padding: 2px 4px; 138 | white-space: nowrap; 139 | overflow: hidden; 140 | text-overflow: ellipsis; 141 | color: inherit; 142 | text-decoration: none; 143 | font-weight: 500; 144 | } 145 | 146 | #auto_generated_birdview_overlay a:first-of-type{ 147 | margin-left: 14px; 148 | } 149 | 150 | #auto_generated_birdview_overlay a:hover{ 151 | color: #fff; 152 | background: #01d1fb; 153 | } 154 | 155 | /* 156 | * 157 | * Close button 158 | * 159 | */ 160 | #auto_generated_birdview_overlay button{ 161 | position: absolute; 162 | width: 96px; 163 | height: 96px; 164 | right: 0; 165 | top: 0; 166 | margin: 0; 167 | padding: 0; 168 | color: transparent; 169 | border: none; 170 | background: transparent no-repeat center center url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RTkxOTI4MTIyNkJGMTFFN0JFQ0M5RTU3MERGOTc4ODUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RTkxOTI4MTMyNkJGMTFFN0JFQ0M5RTU3MERGOTc4ODUiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpFOTE5MjgxMDI2QkYxMUU3QkVDQzlFNTcwREY5Nzg4NSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpFOTE5MjgxMTI2QkYxMUU3QkVDQzlFNTcwREY5Nzg4NSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PusLg80AAAlpSURBVHja7J15cBVF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'); 171 | background-size: 40px; 172 | cursor: pointer; 173 | } 174 | 175 | #auto_generated_birdview_overlay button:hover{ 176 | background-color: #01d1fb; 177 | } 178 | 179 | /* 180 | * 181 | * Help message 182 | * 183 | */ 184 | #auto_generated_birdview_overlay span{ 185 | position: fixed; 186 | left: 0; 187 | bottom: 0; 188 | padding: 16px; 189 | color: #eee; 190 | font-weight: 400; 191 | font-size: 16px; 192 | line-height: 1.4em; 193 | } 194 | 195 | /* 196 | * 197 | * Responsive 198 | * 199 | */ 200 | @media screen and (max-width: 768px){ 201 | #auto_generated_birdview_overlay a{ 202 | max-width: 25%; 203 | font-size: 16px; 204 | } 205 | #auto_generated_birdview_overlay h1{ 206 | margin: 4px 8px 0px; 207 | font-size: 32px; 208 | } 209 | #auto_generated_birdview_overlay a{ 210 | max-width: 25%; 211 | } 212 | #auto_generated_birdview_overlay a:first-of-type{ 213 | margin-left: 6px; 214 | } 215 | #auto_generated_birdview_overlay button{ 216 | width: 72px; 217 | height: 72px; 218 | background-size: 32px; 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/inject/birdview.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////// 2 | // 3 | // Birdview.js 4 | // 1.0 5 | // 6 | // www.achrafkassioui.com/birdview/ 7 | // 8 | // Copyright (C) 2017 Achraf Kassioui 9 | // 10 | // This program is free software: you can redistribute it and/or modify 11 | // it under the terms of the GNU General Public License as published by 12 | // the Free Software Foundation, either version 3 of the License, or any 13 | // later version. 14 | // 15 | // This program is distributed in the hope that it will be useful, 16 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | // GNU General Public License for more details. 19 | // 20 | // https://www.gnu.org/licenses/gpl-3.0.en.html 21 | // 22 | //////////////////////////////////////////////////////////////////////// 23 | 24 | (function(root, factory){ 25 | if(typeof define === 'function' && define.amd){ 26 | define(function(){ 27 | root.birdview = factory(); 28 | return root.birdview; 29 | }); 30 | }else if(typeof exports === 'object'){ 31 | module.exports = factory(); 32 | }else{ 33 | root.birdview = factory(); 34 | } 35 | }(this, function() { 36 | 'use strict'; 37 | 38 | document.addEventListener('birdview:init', function(event) { 39 | birdview.init(event.detail.options || {}); 40 | }); 41 | 42 | document.addEventListener('birdview:toggle', function() { 43 | birdview.toggle(); 44 | }); 45 | 46 | //////////////////////////////////////////////////////////////////////// 47 | // 48 | // Variables 49 | // 50 | //////////////////////////////////////////////////////////////////////// 51 | 52 | var birdview = {}; 53 | var settings; 54 | 55 | var scaled = false; 56 | 57 | var html = document.documentElement; 58 | var body = document.body; 59 | var parent; 60 | var child; 61 | 62 | var birdview_button; 63 | var overlay; 64 | var debug; 65 | 66 | var document_height; 67 | var viewport_height; 68 | var scale_value; 69 | 70 | var css_transform_origin_Y = 0; 71 | 72 | var zoom_level; 73 | var reference_zoom_level; 74 | 75 | var touch = { 76 | startX: 0, 77 | startY: 0, 78 | startSpan: 0, 79 | count: 0 80 | } 81 | 82 | /* 83 | * 84 | * Keycodes that disable birdview. Most are scrolling related keys 85 | * left: 37, up: 38, right: 39, down: 40, spacebar: 32, pageup: 33, pagedown: 34, end: 35, home: 36, esc: 27 86 | * 87 | */ 88 | var scrolling_keys = {37: 1, 38: 1, 39: 1, 40: 1, 32: 1, 33: 1, 34: 1, 35: 1, 36: 1, 27: 1}; 89 | 90 | /* 91 | * 92 | * For feature test 93 | * 94 | */ 95 | var supports = !!body.addEventListener && !!body.style.transition && !!body.style.transform; 96 | 97 | //////////////////////////////////////////////////////////////////////// 98 | // 99 | // Default settings 100 | // 101 | //////////////////////////////////////////////////////////////////////// 102 | 103 | var defaults = { 104 | shortcut: 90, 105 | transition_speed: 0.3, 106 | transition_easing: 'ease', 107 | css_transform_origin_X: 50, 108 | create_button: false, 109 | create_overlay: true, 110 | callback_start: null, 111 | callback_end: null, 112 | debug: false 113 | } 114 | 115 | //////////////////////////////////////////////////////////////////////// 116 | // 117 | // DOM setup 118 | // 119 | //////////////////////////////////////////////////////////////////////// 120 | 121 | /* 122 | * 123 | * Will wrap all body content inside 124 | * 125 | * <div id="birdview_parent"> 126 | * <div id="birdview_child"> 127 | * ... 128 | * </div> 129 | * </div> 130 | * 131 | */ 132 | function setupDOM(){ 133 | wrapAll(body, 'birdview_parent'); 134 | wrapAll('birdview_parent', 'birdview_child'); 135 | parent = document.getElementById('birdview_parent'); 136 | child = document.getElementById('birdview_child'); 137 | if(settings.create_button) createButton(); 138 | if(settings.create_overlay) createOverlay(); 139 | if(settings.debug) createDebug(); 140 | } 141 | 142 | function restoreDOM(){ 143 | unwrap('birdview_child'); 144 | unwrap('birdview_parent'); 145 | child = null; 146 | parent = null; 147 | removeButton(); 148 | removeOverlay(); 149 | removeDebug(); 150 | } 151 | 152 | function createButton(){ 153 | birdview_button = document.createElement('button'); 154 | birdview_button.innerHTML = 'Z'; 155 | birdview_button.id = 'auto_generated_birdview_button'; 156 | birdview_button.classList.add('birdview_toggle'); 157 | body.appendChild(birdview_button); 158 | } 159 | 160 | function removeButton(){ 161 | var button = document.getElementById('auto_generated_birdview_button'); 162 | if(button) button.parentNode.removeChild(button); 163 | } 164 | 165 | function createOverlay(){ 166 | overlay = document.createElement('div'); 167 | overlay.id = 'auto_generated_birdview_overlay'; 168 | if(settings.transition_speed === 0) overlay.style.transitionDuration = '0s'; 169 | else overlay.style.transitionDuration = '0.1s'; 170 | body.appendChild(overlay); 171 | } 172 | 173 | function removeOverlay(){ 174 | var overlay = document.getElementById('auto_generated_birdview_overlay'); 175 | if(overlay) overlay.parentNode.removeChild(overlay); 176 | } 177 | 178 | /* 179 | * 180 | * Creates a div to show debug messages on touch devices. Used with function log(msg) 181 | * 182 | */ 183 | function createDebug(){ 184 | debug = document.createElement('div'); 185 | debug.id = 'birdview_debug'; 186 | debug.innerHTML = 'DEBUG'; 187 | body.appendChild(debug); 188 | } 189 | 190 | function removeDebug(){ 191 | var debug = document.getElementById('birdview_debug'); 192 | if(debug) debug.parentNode.removeChild(debug); 193 | } 194 | 195 | //////////////////////////////////////////////////////////////////////// 196 | // 197 | // Measurements 198 | // 199 | //////////////////////////////////////////////////////////////////////// 200 | 201 | function updateMeasurements(){ 202 | document_height = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight); 203 | viewport_height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); 204 | scale_value = viewport_height / document_height; 205 | } 206 | 207 | /* 208 | * 209 | * Returns the Y transform origin according to scrolling position, viewport hight and document length 210 | * 211 | */ 212 | function birdviewTransformOriginY(){ 213 | return css_transform_origin_Y = ((window.pageYOffset + (viewport_height * 0.5)) / document_height) * 100; 214 | } 215 | 216 | /* 217 | * 218 | * Given a value 'x' in [a, b], output a value 'y' in [c, d] 219 | * 220 | */ 221 | function linearTransform(x, a, b, c, d){ 222 | var y = ((x - a) * (d - c)) / (b - a) + c; 223 | return y; 224 | } 225 | 226 | function compensateScale(){ 227 | var compensate_scale = (linearTransform(css_transform_origin_Y, 0, 100, -1, 1)) * viewport_height * 0.5; 228 | return compensate_scale; 229 | } 230 | 231 | function diveTransformOrigin(click_Y_position){ 232 | return css_transform_origin_Y = ((click_Y_position / viewport_height) * 100); 233 | } 234 | 235 | function diveScrollPosition(click_Y_position){ 236 | var scroll_to = ((click_Y_position / viewport_height) * document_height) - ((click_Y_position / viewport_height) * viewport_height); 237 | return scroll_to; 238 | } 239 | 240 | function currentZoomLevel(){ 241 | var current_zoom_level = screen.width / window.innerWidth; 242 | return current_zoom_level; 243 | } 244 | 245 | function distanceBetween(a,b){ 246 | var dx = a.x - b.x; 247 | var dy = a.y - b.y; 248 | return Math.sqrt( dx*dx + dy*dy ); 249 | } 250 | 251 | //////////////////////////////////////////////////////////////////////// 252 | // 253 | // CSS transformations 254 | // 255 | //////////////////////////////////////////////////////////////////////// 256 | 257 | function birdviewCSS(){ 258 | updateMeasurements(); 259 | parent.style.transition = 'transform ' + settings.transition_speed + 's ' + settings.transition_easing; 260 | child.style.transition = 'transform ' + settings.transition_speed + 's ' + settings.transition_easing; 261 | child.style.transformOrigin = settings.css_transform_origin_X + '% ' + birdviewTransformOriginY() + '%'; 262 | child.style.transform = 'scale(' + scale_value + ')'; 263 | parent.style.transform = 'translateY(' + compensateScale() + 'px)'; 264 | } 265 | 266 | function diveCSS(click_Y_position){ 267 | child.style.transformOrigin = settings.css_transform_origin_X + '% ' + diveTransformOrigin(click_Y_position) + '%'; 268 | child.style.transform = 'scale(1)'; 269 | parent.style.transitionDuration = '0s'; 270 | parent.style.transform = 'translateY(0px)'; 271 | } 272 | 273 | function removeBirdviewCSS(){ 274 | child.style.transformOrigin = settings.css_transform_origin_X + '% ' + css_transform_origin_Y + '%'; 275 | child.style.transform = 'scale(1)'; 276 | parent.style.transform = 'translateY(0px)'; 277 | } 278 | 279 | //////////////////////////////////////////////////////////////////////// 280 | // 281 | // Birdview methods 282 | // 283 | //////////////////////////////////////////////////////////////////////// 284 | 285 | birdview.toggle = function(){ 286 | if(!scaled) enterBirdview(); 287 | else exitBirdview(); 288 | } 289 | 290 | function enterBirdview(){ 291 | if(scaled) return; 292 | if(viewport_height >= document_height){ 293 | console.log('Page already fits in viewport'); 294 | return; 295 | } 296 | scaled = true; 297 | if(settings.create_overlay) toggleOverlay(); 298 | birdviewCSS(); 299 | if(settings.callback_start) settings.callback_start(); 300 | } 301 | 302 | function exitBirdview(){ 303 | if(!scaled) return; 304 | scaled = false; 305 | if(settings.create_overlay) toggleOverlay(); 306 | removeBirdviewCSS(); 307 | if(settings.callback_end) settings.callback_end(); 308 | } 309 | 310 | function dive(click_Y_position){ 311 | if(!scaled) return; 312 | scaled = false; 313 | if(settings.create_overlay) toggleOverlay(); 314 | diveCSS(click_Y_position); 315 | window.scrollTo(0, diveScrollPosition(click_Y_position)); 316 | if(settings.callback_end) settings.callback_end(); 317 | } 318 | 319 | //////////////////////////////////////////////////////////////////////// 320 | // 321 | // User interface 322 | // 323 | //////////////////////////////////////////////////////////////////////// 324 | 325 | function toggleOverlay(){ 326 | if(settings.transition_speed === 0){ 327 | if(scaled) showMenu(); 328 | else hideOverlay(); 329 | } 330 | /* 331 | * 332 | * Handle overlay display with transitionend event 333 | * 334 | */ 335 | else showLoading(); 336 | } 337 | 338 | function showLoading(){ 339 | overlay.classList.add('show'); 340 | overlay.classList.add('zooming'); 341 | overlay.innerHTML = '<h1>Zooming...</h1>'; 342 | if(settings.create_button) birdview_button.classList.remove('hidden'); 343 | } 344 | 345 | function showMenu(){ 346 | if(overlay.classList.contains('zooming')) overlay.classList.remove('zooming'); 347 | if(!overlay.classList.contains('show')) overlay.classList.add('show'); 348 | overlay.innerHTML = '<h1>Birdview</h1><button class="birdview_toggle">X</button>' + addNavigation() + '<span>Click to dive<br>Press Z or pinch to toggle birdview</span>'; 349 | if(settings.create_button) birdview_button.classList.add('hidden'); 350 | } 351 | 352 | function hideOverlay(){ 353 | overlay.innerHTML = ''; 354 | if(overlay.classList.contains('show')) overlay.classList.remove('show'); 355 | if(settings.create_button) birdview_button.classList.remove('hidden'); 356 | } 357 | 358 | function addNavigation(){ 359 | var breadcrumb; 360 | if(location.pathname == "/") breadcrumb = '<a href="/">Home</a>'; 361 | else breadcrumb = '<a href="/">Home</a>/<a href="">' + document.title + '</a>'; 362 | return breadcrumb; 363 | } 364 | 365 | //////////////////////////////////////////////////////////////////////// 366 | // 367 | // Events handlers 368 | // 369 | //////////////////////////////////////////////////////////////////////// 370 | 371 | function eventHandler(e){ 372 | 373 | if(e.type === 'transitionend'){ 374 | if(scaled) showMenu(); 375 | else hideOverlay(); 376 | } 377 | 378 | if(e.type === 'resize' && scaled) birdviewCSS(); 379 | 380 | if(e.type === 'orientationchange') reference_zoom_level = currentZoomLevel(); 381 | 382 | if(e.type === 'keydown'){ 383 | var tag = e.target.tagName; 384 | if(e.keyCode == settings.shortcut && tag != 'INPUT' && tag != 'TEXTAREA' && tag != 'SELECT'){ 385 | birdview.toggle(); 386 | }else if(scrolling_keys[e.keyCode]){ 387 | exitBirdview(); 388 | } 389 | } 390 | 391 | if(e.type === 'click'){ 392 | var target = e.target; 393 | if(target.classList.contains('birdview_toggle') || target.parentNode.classList.contains('birdview_toggle')){ 394 | birdview.toggle(); 395 | }else if(scaled){ 396 | var tag = target.tagName; 397 | if(tag === 'A' || target.parentNode.tagName === 'A'){ 398 | return; 399 | }else if(tag != 'H1' && tag != 'A' && tag != 'BUTTON'){ 400 | dive(e.clientY); 401 | }else if(tag === 'H1'){ 402 | birdview.toggle(); 403 | } 404 | } 405 | } 406 | 407 | if(e.type === 'scroll' || e.type === 'mousewheel' || e.type === 'onwheel' || e.type === 'DOMMouseScroll' || e.type === 'onmousewheel'){ 408 | exitBirdview(); 409 | } 410 | 411 | if(e.type === 'mousedown' && e.which === 2){ 412 | exitBirdview(); 413 | } 414 | }; 415 | 416 | function onTouchStart(e){ 417 | /* 418 | * 419 | * The touch handling logic is inspired from reveal.js https://github.com/hakimel/reveal.js/blob/master/js/reveal.js 420 | * 421 | */ 422 | touch.startX = e.touches[0].clientX; 423 | touch.startY = e.touches[0].clientY; 424 | touch.count = e.touches.length; 425 | 426 | /* 427 | * 428 | * If there are two touches we need to memorize the distance between those two points to detect pinching 429 | * 430 | */ 431 | if(e.touches.length === 2){ 432 | touch.startSpan = distanceBetween({ 433 | x: e.touches[1].clientX, 434 | y: e.touches[1].clientY 435 | },{ 436 | x: touch.startX, 437 | y: touch.startY 438 | }); 439 | } 440 | } 441 | 442 | function onTouchMove(e){ 443 | /* 444 | * 445 | * If in birdview, then disable touch scroll 446 | * 447 | */ 448 | if(scaled) e.preventDefault(); 449 | 450 | /* 451 | * 452 | * We want to trigger birdview with a pinch in, but we don't want to disable the pinch out zoom 453 | * Test the zoom level of the document relative to a reference value stored on first load. Proceed only if the page is not zoomed in 454 | * 455 | */ 456 | zoom_level = currentZoomLevel(); 457 | if(zoom_level != reference_zoom_level) return; 458 | 459 | /* 460 | * 461 | * If the touch started with two points and still has two active touches, test for the pinch gesture 462 | * 463 | */ 464 | if(e.touches.length === 2 && touch.count === 2){ 465 | /* 466 | * 467 | * The current distance in pixels between the two touch points 468 | * 469 | */ 470 | var currentSpan = distanceBetween({ 471 | x: e.touches[1].clientX, 472 | y: e.touches[1].clientY 473 | },{ 474 | x: touch.startX, 475 | y: touch.startY 476 | }); 477 | 478 | /* 479 | * 480 | * If user starts pinching in, disable default browser behavior 481 | * 482 | */ 483 | if(currentSpan <= touch.startSpan){ 484 | e.preventDefault(); 485 | } 486 | 487 | /* 488 | * 489 | * If the span is larger than the desired amount, toggle birdview 490 | * 491 | */ 492 | if(Math.abs( touch.startSpan - currentSpan ) > 30 ){ 493 | if(currentSpan < touch.startSpan){ 494 | enterBirdview(); 495 | }else{ 496 | /* 497 | * 498 | * In birdview and if the user pinches out, dive into the Y mid point between the two touches 499 | * 500 | */ 501 | dive( (touch.startY + e.touches[1].clientY) * 0.5 ); 502 | } 503 | } 504 | } 505 | } 506 | 507 | //////////////////////////////////////////////////////////////////////// 508 | // 509 | // Utility functions 510 | // 511 | //////////////////////////////////////////////////////////////////////// 512 | 513 | function extend(defaults, options) { 514 | var extended = {}; 515 | var prop; 516 | for(prop in defaults){ 517 | if (Object.prototype.hasOwnProperty.call(defaults, prop)){ 518 | extended[prop] = defaults[prop]; 519 | } 520 | } 521 | for(prop in options){ 522 | if(Object.prototype.hasOwnProperty.call(options, prop)){ 523 | extended[prop] = options[prop]; 524 | } 525 | } 526 | return extended; 527 | } 528 | 529 | function wrapAll(parent, wrapper_id){ 530 | if(parent != body) var parent = document.getElementById(parent); 531 | var wrapper = document.createElement('div'); 532 | wrapper.id = wrapper_id; 533 | while (parent.firstChild) wrapper.appendChild(parent.firstChild); 534 | parent.appendChild(wrapper); 535 | } 536 | 537 | function unwrap(wrapper){ 538 | var wrapper = document.getElementById(wrapper); 539 | var parent = wrapper.parentNode; 540 | while (wrapper.firstChild) parent.insertBefore(wrapper.firstChild, wrapper); 541 | parent.removeChild(wrapper); 542 | } 543 | 544 | function log(message){ 545 | if(debug) debug.innerHTML = message; 546 | } 547 | 548 | //////////////////////////////////////////////////////////////////////// 549 | // 550 | // Initialize 551 | // 552 | //////////////////////////////////////////////////////////////////////// 553 | 554 | birdview.init = function(options){ 555 | 556 | if(!!supports) return; 557 | 558 | birdview.destroy(); 559 | 560 | settings = extend(defaults, options || {} ); 561 | 562 | setupDOM(); 563 | 564 | updateMeasurements(); 565 | 566 | reference_zoom_level = currentZoomLevel(); 567 | 568 | if(settings.transition_speed != 0) child.addEventListener("transitionend", eventHandler, false); 569 | 570 | /* 571 | * 572 | * Active event listeners. See: https://developers.google.com/web/updates/2017/01/scrolling-intervention 573 | * 574 | */ 575 | if('ontouchstart' in window){ 576 | document.addEventListener('touchstart', onTouchStart, {passive: false}); 577 | document.addEventListener('touchmove', onTouchMove, {passive: false}); 578 | }else{ 579 | } 580 | 581 | document.addEventListener('keydown', eventHandler, false); 582 | 583 | document.addEventListener('click', eventHandler, false); 584 | 585 | window.addEventListener('scroll', eventHandler, false); 586 | 587 | window.addEventListener('resize', eventHandler, false); 588 | 589 | window.addEventListener("orientationchange", eventHandler, false); 590 | 591 | console.log('Birdview is running. Press Z'); 592 | }; 593 | 594 | //////////////////////////////////////////////////////////////////////// 595 | // 596 | // Destroy 597 | // 598 | //////////////////////////////////////////////////////////////////////// 599 | 600 | birdview.destroy = function(){ 601 | 602 | if(!settings) return; 603 | 604 | if(settings.transition_speed != 0) child.removeEventListener("transitionend", eventHandler, false); 605 | 606 | restoreDOM(); 607 | 608 | reference_zoom_level = null; 609 | 610 | if('ontouchstart' in window){ 611 | document.removeEventListener('touchstart', onTouchStart, {passive: false}); 612 | document.removeEventListener('touchmove', onTouchMove, {passive: false}); 613 | }else{ 614 | } 615 | 616 | document.removeEventListener('keydown', eventHandler, false); 617 | 618 | document.removeEventListener('click', eventHandler, false); 619 | 620 | window.removeEventListener('scroll', eventHandler, false); 621 | 622 | window.removeEventListener('resize', eventHandler, false); 623 | 624 | window.removeEventListener("orientationchange", eventHandler, false); 625 | 626 | scaled = false; 627 | settings = null; 628 | 629 | console.log('Birdview was destroyed'); 630 | } 631 | 632 | return birdview; 633 | })); 634 | -------------------------------------------------------------------------------- /src/inject/inject.js: -------------------------------------------------------------------------------- 1 | var hasLoaded = false; 2 | var event; 3 | chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { 4 | if (request.action == 'icon-clicked') { 5 | if (hasLoaded) { 6 | event = new Event('birdview:toggle'); 7 | document.dispatchEvent(event); 8 | } else { 9 | hasLoaded = true; 10 | 11 | var link = document.createElement('link'); 12 | link.rel = 'stylesheet'; 13 | link.type = 'text/css'; 14 | link.href = chrome.extension.getURL('src/inject/birdview.css'); 15 | document.head.appendChild(link); 16 | 17 | var script = document.createElement('script'); 18 | script.src = chrome.extension.getURL('src/inject/birdview.js'); 19 | script.onload = function() { 20 | chrome.storage.sync.get( 21 | null, // ie; get everything 22 | function(items) { 23 | event = new CustomEvent('birdview:init', { detail: { options: items }}); 24 | document.dispatchEvent(event); 25 | event = new Event('birdview:toggle'); 26 | document.dispatchEvent(event); 27 | } 28 | ); 29 | } 30 | document.head.appendChild(script); 31 | } 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /src/options.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html> 3 | <head> 4 | <title>Birdview Options 5 | 15 | 16 | 17 | 18 |
 
19 |
20 | 21 | 22 |
23 | Documentation 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/options.js: -------------------------------------------------------------------------------- 1 | var options = [ 2 | { 3 | display: 'Shortcut (keycode)', 4 | id: 'shortcut', 5 | default: 90, 6 | type: 'number' 7 | }, 8 | { 9 | display: 'Show overlay', 10 | id: 'create_overlay', 11 | default: true, 12 | type: 'checkbox' 13 | }, 14 | { 15 | display: 'Transition time (seconds)', 16 | id: 'transition_speed', 17 | default: 0.3, 18 | type: 'number' 19 | }, 20 | { 21 | display: 'Transition Easing (see http://easings.net)', 22 | id: 'transition_easing', 23 | default: 'ease', 24 | type: 'text' 25 | }, 26 | { 27 | display: 'Horizontal position (%)', 28 | id: 'css_transform_origin_X', 29 | default: 50, 30 | type: 'number' 31 | } 32 | ]; 33 | 34 | // Saves options to chrome.storage.sync. 35 | function saveOptions(valuesHash) { 36 | chrome.storage.sync.set( 37 | valuesHash, 38 | function() { 39 | // Update status to let user know options were saved. 40 | var status = document.querySelector('#status'); 41 | status.innerHTML = 'Options saved'; 42 | setTimeout(function() { 43 | status.innerHTML = ' '; 44 | }, 2000); 45 | } 46 | ); 47 | } 48 | 49 | function getValueType(type) { 50 | if (type === 'checkbox') { 51 | return 'checked'; 52 | } else { 53 | return 'value'; 54 | } 55 | } 56 | 57 | function getValueAttr(type, value) { 58 | if (type === 'checkbox' && !value) { 59 | return ''; 60 | } 61 | return getValueType(type) + '="' + value + '"'; 62 | } 63 | 64 | function createNodeFromString(str) { 65 | var div = document.createElement('div'); 66 | div.innerHTML = str; 67 | return div.firstChild; 68 | } 69 | 70 | function loadOptions() { 71 | chrome.storage.sync.get( 72 | // Generate hash of default values 73 | options.reduce(function(memo, option) { 74 | memo[option.id] = option.default; 75 | return memo; 76 | }, {}), 77 | function(items) { 78 | var containerEl = document.querySelector('#container'); 79 | 80 | options.forEach(function createOption(option) { 81 | var html; 82 | var valueAttr = getValueAttr(option.type, items[option.id]); 83 | var element = containerEl.querySelector('#' + option.id); 84 | if (element) { 85 | element[getValueType(option.type)] = items[option.id]; 86 | } else { 87 | html = ''; 88 | 89 | containerEl.appendChild(createNodeFromString(html)); 90 | } 91 | }); 92 | } 93 | ); 94 | } 95 | 96 | document.getElementById('save').addEventListener('click', function() { 97 | var valuesHash = options.reduce(function(memo, option) { 98 | memo[option.id] = document.querySelector('#' + option.id)[getValueType(option.type)]; 99 | return memo; 100 | }, {}); 101 | saveOptions(valuesHash); 102 | }); 103 | 104 | document.getElementById('defaults').addEventListener('click', function() { 105 | var valuesHash = options.reduce(function(memo, option) { 106 | memo[option.id] = option.default; 107 | return memo; 108 | }, {}); 109 | saveOptions(valuesHash); 110 | loadOptions(); 111 | }); 112 | 113 | loadOptions(); 114 | --------------------------------------------------------------------------------