Skip to content

Instantly share code, notes, and snippets.

@bennadel
Created November 22, 2019 21:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bennadel/6c9fa2c79340db17758dbe73a1d50a69 to your computer and use it in GitHub Desktop.
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
// 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 );
}
}
// 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