1// thanks to https://codepen.io/Martins-shenanigans/pen/qEbzPVq 2(() => { 3 const DURATION = 150; 4 5 if (document.getElementById("FlyingFocus")) return; 6 7 const flyingFocus = document.createElement("div"); 8 flyingFocus.id = "FlyingFocus"; 9 document.body.appendChild(flyingFocus); 10 11 let staticCounter; 12 let hidden = true; 13 let keydownTime = 0; 14 let prevFocused = null; 15 let prevOffset = {}; 16 let focusTimeout; 17 let hideTimeout; 18 let positionTimeout; 19 20 const alwaysFocus = (target) => { 21 // Konfigurierbar: bei Bedarf anpassen 22 return false; 23 }; 24 25 const getBounds = (elem) => { 26 const rect = elem.getBoundingClientRect(); 27 const docElem = document.documentElement; 28 const win = window; 29 const body = document.body; 30 31 const scrollTop = win.pageYOffset || docElem.scrollTop || body.scrollTop; 32 const scrollLeft = win.pageXOffset || docElem.scrollLeft || body.scrollLeft; 33 const clientTop = docElem.clientTop || body.clientTop || 0; 34 const clientLeft = docElem.clientLeft || body.clientLeft || 0; 35 36 const top = rect.top + scrollTop - clientTop; 37 const left = rect.left + scrollLeft - clientLeft; 38 39 return { 40 top, 41 left, 42 width: rect.width, 43 height: rect.height, 44 bottom: top + rect.height, 45 right: left + rect.width, 46 }; 47 }; 48 49 const getInputBounds = (element) => { 50 const elements = [element]; 51 const id = element.getAttribute("id"); 52 const labels = id 53 ? Array.from( 54 document.querySelectorAll(`label[for="${id}"]:not(.visuallyhidden)`) 55 ) 56 : []; 57 58 elements.push(...labels); 59 60 const bounds = { 61 top: Infinity, 62 left: Infinity, 63 bottom: -Infinity, 64 right: -Infinity, 65 }; 66 67 for (const el of elements) { 68 const b = getBounds(el); 69 bounds.top = Math.min(bounds.top, b.top); 70 bounds.left = Math.min(bounds.left, b.left); 71 bounds.bottom = Math.max(bounds.bottom, b.bottom); 72 bounds.right = Math.max(bounds.right, b.right); 73 } 74 75 bounds.width = bounds.right - bounds.left; 76 bounds.height = bounds.bottom - bounds.top; 77 78 return bounds; 79 }; 80 81 const reposition = (target) => { 82 if (!target || hidden) return; 83 84 const offset = getInputBounds(target); 85 86 if ( 87 offset.top !== prevOffset.top || 88 offset.left !== prevOffset.left || 89 offset.width !== prevOffset.width || 90 offset.height !== prevOffset.height 91 ) { 92 flyingFocus.style.left = `${offset.left}px`; 93 flyingFocus.style.top = `${offset.top}px`; 94 flyingFocus.style.width = `${offset.width}px`; 95 flyingFocus.style.height = `${offset.height}px`; 96 } 97 98 return offset; 99 }; 100 101 const show = () => { 102 clearTimeout(hideTimeout); 103 clearTimeout(positionTimeout); 104 105 flyingFocus.style.transitionDuration = hidden 106 ? "0s" 107 : `${DURATION / 1000}s`; 108 109 staticCounter = 0; 110 positionTimeout = setTimeout(checkPosition, 100); 111 112 if (hidden) { 113 flyingFocus.classList.add("FlyingFocus-visible"); 114 hidden = false; 115 } 116 }; 117 118 const hide = () => { 119 if (!hidden) { 120 clearTimeout(focusTimeout); 121 clearTimeout(hideTimeout); 122 hideTimeout = setTimeout(hideReally, 10); 123 } 124 }; 125 126 const hideReally = () => { 127 flyingFocus.classList.remove("FlyingFocus-visible"); 128 hidden = true; 129 clearTimeout(positionTimeout); 130 }; 131 132 const checkPosition = () => { 133 const offset = reposition(prevFocused); 134 if ( 135 offset.top !== prevOffset.top || 136 offset.left !== prevOffset.left || 137 offset.width !== prevOffset.width || 138 offset.height !== prevOffset.height 139 ) { 140 prevOffset = offset; 141 staticCounter = 0; 142 } else { 143 staticCounter++; 144 } 145 146 positionTimeout = setTimeout(checkPosition, staticCounter < 3 ? 100 : 1000); 147 }; 148 149 document.documentElement.addEventListener( 150 "keydown", 151 (event) => { 152 if (event.keyCode < 65) { 153 keydownTime = new Date(); 154 document.body.classList.add("user-is-tabbing"); 155 } 156 }, 157 true 158 ); 159 160 document.addEventListener("focusin", (event) => { 161 focusTimeout = setTimeout(() => { 162 const override = alwaysFocus(event.target); 163 if (!override) { 164 const focusTime = new Date(); 165 if (hidden && focusTime - keydownTime > 100) { 166 return; 167 } 168 } 169 170 show(); 171 prevOffset = reposition(event.target); 172 prevFocused = event.target; 173 }, 1); 174 }); 175 176 document.addEventListener("focusout", hide); 177 178 document.documentElement.addEventListener("mousedown", (event) => { 179 document.body.classList.remove("user-is-tabbing"); 180 if (!alwaysFocus(event.target) && event.detail) { 181 hideReally(); 182 } 183 }); 184 185 document.documentElement.addEventListener("touchstart", (event) => { 186 document.body.classList.remove("user-is-tabbing"); 187 if (!alwaysFocus(event.target) && event.detail) { 188 hideReally(); 189 } 190 }); 191 192 window.addEventListener("resize", () => reposition(prevFocused)); 193})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.