Created
November 22, 2019 21:38
-
-
Save bennadel/6c9fa2c79340db17758dbe73a1d50a69 to your computer and use it in GitHub Desktop.
Having Fun With Position: Fixed And Element.getBoundingClientRect() In Angular 9.0.0-rc.2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Import the core angular services. | |
import { Component } from "@angular/core"; | |
// ----------------------------------------------------------------------------------- // | |
// ----------------------------------------------------------------------------------- // | |
interface Overlay { | |
left: number; | |
height: number; | |
top: number; | |
width: number; | |
} | |
// This class will be injected into the DOM in order to identify which element is | |
// currently being "focused" by the overlay. This has no technical function - it just | |
// makes it easier to see what is happening in the Elements panel of the dev-tools. | |
var TRACER_CLASS = "-+-+-+-+-+-+-+-target-element-+-+-+-+-+-+-+-"; | |
@Component({ | |
selector: "app-root", | |
host: { | |
"(document:click)": "handleClick( $event )", | |
"(window:keydown.Esc)": "reset()", | |
"(window:resize)": "reset()" | |
}, | |
styleUrls: [ "./app.component.less" ], | |
template: | |
` | |
<!-- | |
Just moving the "demo content" to another component so that we don't | |
have a ton of noise in the app component. | |
--> | |
<app-demo-content></app-demo-content> | |
<app-overlay | |
*ngIf="targetOverlay" | |
[height]="targetOverlay.height" | |
[left]="targetOverlay.left" | |
[top]="targetOverlay.top" | |
[width]="targetOverlay.width"> | |
</app-overlay> | |
` | |
}) | |
export class AppComponent { | |
public targetOverlay: Overlay | null; | |
private targetElement: HTMLElement | null; | |
// I initialize the app component. | |
constructor() { | |
this.targetOverlay = null; | |
this.targetElement = null; | |
} | |
// --- | |
// PUBLIC METHODS. | |
// --- | |
// I handle the mouse click on the document. | |
public handleClick( event: MouseEvent ) : void { | |
// If we have an existing element, remove the tracer class. | |
// -- | |
// NOTE: This class serves no functional purpose other than to indicate where in | |
// the DOM tree the selected target lives. This will show up in the Elements pane | |
// of the Chrome Dev Tools, and will clearly illustrate the journey of the | |
// selection as the user continues to click within a single DOM branch. | |
if ( this.targetElement ) { | |
this.targetElement.classList.remove( TRACER_CLASS ); | |
} | |
this.targetElement = this.getNextTarget( event ); | |
this.targetOverlay = null; | |
if ( this.targetElement ) { | |
this.targetElement.classList.add( TRACER_CLASS ); | |
// The bounding client rectangle contains the FIXED location of the given | |
// element within the browser's viewport. | |
var rect = this.targetElement.getBoundingClientRect(); | |
this.targetOverlay = { | |
height: rect.height, | |
left: rect.left, | |
top: rect.top, | |
width: rect.width | |
}; | |
} | |
} | |
// I reset the state, clearing the target element and overlay. | |
public reset() : void { | |
this.targetElement = null; | |
this.targetOverlay = null; | |
} | |
// --- | |
// PRIVATE METHODS. | |
// --- | |
// I get the next target element based on the given mouse click event. | |
private getNextTarget( event: MouseEvent ) : HTMLElement | null { | |
var target = ( event.target as HTMLElement ); | |
// If we have an existing target element and the user clicked below the target | |
// element but within the same DOM branch, let's move up one level in the DOM | |
// tree. | |
if ( this.targetElement && this.targetElement.contains( target ) ) { | |
return( this.targetElement.parentElement || null ); | |
} | |
// Otherwise, just use the user's click target. | |
return( target ); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Import the core angular services. | |
import { Component } from "@angular/core"; | |
// ----------------------------------------------------------------------------------- // | |
// ----------------------------------------------------------------------------------- // | |
@Component({ | |
selector: "app-overlay", | |
inputs: [ | |
"height", | |
"left", | |
"top", | |
"width" | |
], | |
styleUrls: [ "./overlay.component.less" ], | |
template: | |
` | |
<div | |
class="target" | |
[style.opacity]="1" | |
[style.top.px]="fixedTop" | |
[style.right.px]="fixedRight" | |
[style.bottom.px]="fixedBottom" | |
[style.left.px]="fixedLeft"> | |
</div> | |
<div | |
class="vertical-offset" | |
[style.opacity]="( ( fixedTop > 30 ) ? 1 : 0 )" | |
[style.top.px]="0" | |
[style.right.px]="fixedRight" | |
[style.left.px]="fixedLeft" | |
[style.height.px]="fixedTop"> | |
<span class="size"> | |
{{ fixedTop }} | |
</span> | |
</div> | |
<div | |
class="vertical-offset" | |
[style.opacity]="( ( fixedBottom > 30 ) ? 1 : 0 )" | |
[style.right.px]="fixedRight" | |
[style.bottom.px]="0" | |
[style.left.px]="fixedLeft" | |
[style.height.px]="fixedBottom"> | |
<span class="size"> | |
{{ fixedBottom }} | |
</span> | |
</div> | |
<div | |
class="horizontal-offset" | |
[style.opacity]="( ( fixedRight > 50 ) ? 1 : 0 )" | |
[style.top.px]="top" | |
[style.right.px]="0" | |
[style.bottom.px]="fixedBottom" | |
[style.width.px]="fixedRight"> | |
<span class="size"> | |
{{ fixedRight }} | |
</span> | |
</div> | |
<div | |
class="horizontal-offset" | |
[style.opacity]="( ( fixedLeft > 50 ) ? 1 : 0 )" | |
[style.top.px]="fixedTop" | |
[style.bottom.px]="fixedBottom" | |
[style.left.px]="0" | |
[style.width.px]="fixedLeft"> | |
<span class="size"> | |
{{ fixedLeft }} | |
</span> | |
</div> | |
` | |
}) | |
export class OverlayComponent { | |
public height!: number; | |
public left!: number; | |
public top!: number; | |
public width!: number; | |
public fixedBottom: number; | |
public fixedLeft: number; | |
public fixedRight: number; | |
public fixedTop: number; | |
// I initialize the overlay component. | |
constructor() { | |
this.fixedBottom = 0; | |
this.fixedLeft = 0; | |
this.fixedRight = 0; | |
this.fixedTop = 0; | |
} | |
// --- | |
// PUBLIC METHODS. | |
// --- | |
// I get called whenever the input bindings change. | |
public ngOnChanges() : void { | |
// Translate the input bindings to "fixed" coordinates. | |
// -- | |
// NOTE: The Top/Left inputs are already intended to be "fixed", but we need to | |
// calculate the Right/Bottom based on the dimensions of the window. | |
this.fixedTop = Math.floor( this.top ); | |
this.fixedLeft = Math.floor( this.left ); | |
this.fixedRight = Math.floor( document.documentElement.clientWidth - ( this.left + this.width ) ); | |
this.fixedBottom = Math.floor( document.documentElement.clientHeight - ( this.top + this.height ) ); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment