|
| 1 | +<!doctype html> |
| 2 | +<html lang="en"> |
| 3 | +<head> |
| 4 | + <meta charset="utf-8" /> |
| 5 | + <title> |
| 6 | + Animating DOM Rectangles Over Focused Elements In JavaScript |
| 7 | + </title> |
| 8 | + <link rel="stylesheet" type="text/css" href="./main.css" /> |
| 9 | +</head> |
| 10 | +<body> |
| 11 | + |
| 12 | + <h1> |
| 13 | + Animating DOM Rectangles Over Focused Elements In JavaScript |
| 14 | + </h1> |
| 15 | + |
| 16 | + <p> |
| 17 | + Before demo <a href>text block</a> (to take focus from demo block and to contrast |
| 18 | + the normal focus-outline behavior). |
| 19 | + </p> |
| 20 | + |
| 21 | + <p class="textBlock"> |
| 22 | + <mark hidden class="box"> |
| 23 | + <!-- |
| 24 | + I will scurry across the viewport and highlight focused areas within this |
| 25 | + paragraph. Since focused text may wrap across line-breaks, I'm providing |
| 26 | + several internal segments for compound highlighting rectangles. |
| 27 | + --> |
| 28 | + <span></span> |
| 29 | + <span></span> |
| 30 | + <span></span> |
| 31 | + </mark> |
| 32 | + |
| 33 | + <span data-repeat="10"> |
| 34 | + Lorem ipsum dolor <a href>sit amet</a> interrogo communis flumen. |
| 35 | + <a href>Mille iuvenis</a>, umquam ante cohors, adhibeo citus fortis provincia. |
| 36 | + Hic posco ego, quis frequens tenebrae. Aliquis turbo is epistula. <a href>Hic |
| 37 | + experior quidam voluntas</a> nam aliquis vinculum. Noster puto paulo sum post |
| 38 | + saevus natura. Diversus ventus, quasi cum for meus, scribo dexter prior |
| 39 | + astrum. Idem condo qua ictus quoque nemo iter. Mortalis frater, denique in |
| 40 | + puer, tollo nullus quattuor vestigium. <a href>Iste dico ipse voluntas an sui |
| 41 | + vitium meus desino quisquis, qua gratus ira tu opto nemo praemium gens rideo |
| 42 | + diversus error</a>. |
| 43 | + </span> |
| 44 | + </p> |
| 45 | + |
| 46 | + <style type="text/css"> |
| 47 | + |
| 48 | + mark.box span { |
| 49 | + background-color: transparent ; |
| 50 | + border-radius: 3px ; |
| 51 | + border: 1px solid tomato ; |
| 52 | + box-shadow: inset 0 0 0 3px #ffff04aa ; |
| 53 | + opacity: 0 ; |
| 54 | + pointer-events: none ; |
| 55 | + position: absolute ; |
| 56 | + transition: all 100ms ease-out ; |
| 57 | + z-index: 9999 ; |
| 58 | + } |
| 59 | + |
| 60 | + </style> |
| 61 | + <script type="text/javascript"> |
| 62 | + |
| 63 | + var box = document.querySelector( "mark.box" ); |
| 64 | + var boxSegments = Array.from( box.children ); |
| 65 | + |
| 66 | + // When a new element is focused within the document, we want zoom the marker |
| 67 | + // box(es) over to the focused element to emphasize focus. |
| 68 | + document.addEventListener( "focusin", ( event ) => { |
| 69 | + |
| 70 | + var target = event.target; |
| 71 | + |
| 72 | + // If an element OUTSIDE of the demo text block was focused, hide the marker - |
| 73 | + // we only want that to show the crazy boxy inside the demo block. |
| 74 | + if ( ! target.closest( ".textBlock" ) ) { |
| 75 | + |
| 76 | + box.hidden = true; |
| 77 | + return; |
| 78 | + |
| 79 | + } |
| 80 | + |
| 81 | + // We're about to animate the box segments into place, mark the box as active |
| 82 | + // (and visible) in the DOM. |
| 83 | + box.hidden = false; |
| 84 | + |
| 85 | + // Instead of using the `.getBoundingClientRect()`, which will give us the box |
| 86 | + // that encompasses ALL areas of the focused text, we're going to use the |
| 87 | + // `.getClientRects()` to get individual DOMRect readings for each part of the |
| 88 | + // text if it wraps across lines. |
| 89 | + // -- |
| 90 | + // Note: if the text doesn't wrap, the two get-rect methods are equivalent. |
| 91 | + var rects = Array.from( target.getClientRects() ); |
| 92 | + var offsetBlock = window.scrollY; |
| 93 | + var offsetInline = window.scrollX; |
| 94 | + var padBlock = 1; |
| 95 | + var padInline = 6; |
| 96 | + |
| 97 | + boxSegments.forEach( |
| 98 | + ( segment, i ) => { |
| 99 | + |
| 100 | + var rect = rects[ i ]; |
| 101 | + // In order to make the animations smooth, we want to position every |
| 102 | + // segment even if they aren't going to be rendered. This way, when |
| 103 | + // they do get rendered, it looks like everything originates from the |
| 104 | + // same location within the viewport. To that end, if we don't have a |
| 105 | + // matching DOMRect for this segment, just use the first one and mark |
| 106 | + // the segment as translucent. |
| 107 | + if ( rect ) { |
| 108 | + |
| 109 | + segment.style.opacity = 1; // Show. |
| 110 | + |
| 111 | + } else { |
| 112 | + |
| 113 | + segment.style.opacity = 0; // Hide. |
| 114 | + rect = rects[ 0 ]; |
| 115 | + |
| 116 | + } |
| 117 | + |
| 118 | + segment.style.left = `${ rect.left - padInline + offsetInline }px`; |
| 119 | + segment.style.top = `${ rect.top - padBlock + offsetBlock }px`; |
| 120 | + segment.style.width = `${ rect.width + ( padInline * 2 ) }px`; |
| 121 | + segment.style.height = `${ rect.height + ( padBlock * 2 ) }px`; |
| 122 | + |
| 123 | + } |
| 124 | + ); |
| 125 | + |
| 126 | + }); |
| 127 | + |
| 128 | + // When the window loses focus, hide the marker box. |
| 129 | + window.addEventListener( "blur", ( event ) => { |
| 130 | + |
| 131 | + box.hidden = true; |
| 132 | + |
| 133 | + }); |
| 134 | + |
| 135 | + // --------------------------------------------------------------------------- // |
| 136 | + // --------------------------------------------------------------------------- // |
| 137 | + // Flesh-out lots of text for the demo so we can force window scrolling. |
| 138 | + // --------------------------------------------------------------------------- // |
| 139 | + // --------------------------------------------------------------------------- // |
| 140 | + |
| 141 | + var templateNode = document.querySelector( "[data-repeat]" ); |
| 142 | + var count = Number( templateNode.dataset.repeat ); |
| 143 | + |
| 144 | + for ( var i = 1 ; i <= count ; i++ ) { |
| 145 | + |
| 146 | + templateNode.after( templateNode.cloneNode( true ) ); |
| 147 | + |
| 148 | + } |
| 149 | + |
| 150 | + // Don't honor any anchor links. I want the `<a>` tags to have something to focus; |
| 151 | + // but I don't want the clicks to actually navigate away from the page or offset. |
| 152 | + document.addEventListener( "click", ( event ) => { |
| 153 | + |
| 154 | + if ( event.target.closest( "a" ) ) { |
| 155 | + |
| 156 | + event.preventDefault(); |
| 157 | + |
| 158 | + } |
| 159 | + |
| 160 | + }); |
| 161 | + |
| 162 | + </script> |
| 163 | + |
| 164 | +</body> |
| 165 | +</html> |
0 commit comments