diff --git a/content/blog/css-stacking-context/index.md b/content/blog/css-stacking-context/index.md index 54f78fab..be9a6eb9 100644 --- a/content/blog/css-stacking-context/index.md +++ b/content/blog/css-stacking-context/index.md @@ -1,13 +1,12 @@ --- { - title: "Portals", + title: "Why is z-index not working?! - Explaining CSS Stacking Context", description: "", published: '2023-01-01T22:12:03.284Z', authors: ['crutchcorn'], - tags: ['webdev'], + tags: ['webdev', 'css', 'html'], attached: [], - order: 15, - series: "The Framework Field Guide" + license: 'cc-by-4' } --- @@ -15,7 +14,7 @@ While building sufficiently useful modals can be a challenging task, a rudimentary modal can be completed even without JavaScript. -While we'll loop back to JavaScript (using React, Angular, and Vue) in a bit, let's use some CSS and HTML in order to build a basic modal: +Let's use some CSS and HTML in order to build a basic modal: ```html
@@ -240,7 +239,7 @@ Notice how the purple box seemingly remains on "top" when we re-arrange the CSS Well... -### Re-arrange HTML Elements to Change the Stacking Order +# Re-arrange HTML Elements to Change the Stacking Order Let's take the HTML we had before, and re-arrange it a bit: @@ -258,7 +257,7 @@ Now if we look at the box order, we'll see... Now our boxes have reversed their height order! This is because one of the deciding factors of an element's `z` position is its relationship to other elements. -### Positioned Elements Behave Differently Than Non-Positioned Elements +# Positioned Elements Behave Differently Than Non-Positioned Elements > This is where things get confusing. Take your time with this chapter, it's okay to have to re-read this section multiple times. @@ -343,7 +342,7 @@ While our green button now smoothly moves left when you hover over it, there's a This is because positioning an element introduces a "stacked context". This means that our `relative` positioned element takes priority in the `z` layer over non-positioned elements. -### Understanding more rules of Stacked Contexts +# Understanding more rules of Stacked Contexts While `relative` positioning is one way that you can take priority in a stacked context, it's far from the only way to do so. Here's a list of CSS rules that will take priority in a stacked context, from the lowest priority to the highest priority: @@ -401,7 +400,7 @@ You would see the following order of elements: This is because the `lime` and `slate` take priority over `yellow` and `cyan` thanks to their `relative` positioning, but are still in HTML order within the same `z` level priority and within the same stacking context. -### Creating Stacking Contexts +# Creating Stacking Contexts > "Welp, that's enough reading in the book today" @@ -523,17 +522,17 @@ Then it will show "Absolute" above "Opacity", thanks to the order of the HTML se If we remove the `opacity: 0.99` from the `"Opacity"` `div`, then `"Absolute`" will be on top. -### Stacking Stacking Contexts +# Stacking Stacking Contexts While the previous sections have been head scratchers, let's dive into mind melting territory: You can contain stacking contexts within other stacking contexts. 🤯 +// TODO: Write +# The Problem with Stacking Contexts -### The Problem with Stacking Contexts - - +// TODO: Explain that `z-index` cannot escape @@ -545,368 +544,3 @@ While the previous sections have been head scratchers, let's dive into mind melt > - [What No One Told You About Z-Index - Philip Walton](https://philipwalton.com/articles/what-no-one-told-you-about-z-index/) > - [Appendix E. Elaborate description of Stacking Contexts - W3C](https://www.w3.org/TR/CSS2/zindex.html) -# What is a JavaScript portals? - -> What does any of that CSS stuff have to do with my JavaScript?! - -First: Tone. Second: Everything. - - - -# Using Local Portals - -// TODO: Write this - - - - - -## React - -// TODO: Write this - -```jsx -import React, { useMemo, useState } from 'react'; -import ReactDOM from 'react-dom'; - -export default function App() { - const [portalRef, setPortalRef] = useState(null); - - const portal = useMemo(() => { - if (!portalRef) return null; - return ReactDOM.createPortal(
Hello, world!
, portalRef); - }, [portalRef]); - - return ( - <> -
setPortalRef(el)} - style={{ height: '100px', width: '100px', border: '2px solid black' }} - > -
-
- {portal} - - ); -} -``` - - - -## Angular - -While the other frameworks have something akin to a portal system built into their frameworks' core, Angular does not. Instead, the Angular team maintains a library called "Angular CDK" in order to have shared UI code for utilities such as portals. - -To use the Angular CDK, you'll first need to install it into your project: - -``` -npm i @angular/cdk -``` - -From here, we can import components and utilities directly from the CDK. - -```typescript -import { PortalModule } from '@angular/cdk/portal'; -import { DomPortal } from '@angular/cdk/portal'; - -@Component({ - selector: 'my-app', - template: ` -
- -
-
Hello, world!
- `, -}) -class AppComponent implements AfterViewInit { - @ViewChild('portalContent') portalContent: ElementRef; - - domPortal: DomPortal; - - ngAfterViewInit() { - // This is to avoid an: - // "Expression has changed after it was checked" - // error when trying to set domPortal - setTimeout(() => { - this.domPortal = new DomPortal(this.portalContent); - }); - } -} - -@NgModule({ - declarations: [AppComponent], - imports: [BrowserModule, PortalModule], - providers: [], - bootstrap: [AppComponent], -}) -export class AppModule {} -``` - -### Rendering `ng-template` - -There might be a flash of the `div` on screen before our `ngAfterViewInit` occurs. As such, we may want to use an `ng-template`: - -// TODO: Write - -```typescript -import { PortalModule, TemplatePortal } from '@angular/cdk/portal'; - -@Component({ - selector: 'my-app', - template: ` -
- -
- Hello, this is a template portal - `, -}) -class AppComponent implements AfterViewInit { - @ViewChild('portalContent') portalContent: TemplateRef; - - viewContainerRef = inject(ViewContainerRef); - domPortal: TemplatePortal; - - ngAfterViewInit() { - // This is to avoid an: - // "Expression has changed after it was checked" - // error when trying to set domPortal - setTimeout(() => { - this.domPortal = new TemplatePortal( - this.portalContent, - this.viewContainerRef - ); - }); - } -} -``` - - - - - -## Vue - -// TODO: Write this - -```vue - - - - -``` - -We need this `v-if` in order to ensure that `portalContainerEl` has already been rendered and is ready to project content. - - - -// TODO: Write this - - - - - -# Application-Wide Portals - -// TODO: Write this - - - -## React - -// TODO: Write this - -```jsx -import React, { useState, createContext, useContext } from 'react'; -import ReactDOM from 'react-dom'; - -// We start by creating a context name -const PortalContext = React.createContext(); - -function ChildComponent() { - const portalRef = useContext(PortalContext); - if (!portalRef) return null; - return ReactDOM.createPortal(
Hello, world!
, portalRef); -} - -export default function App() { - const [portalRef, setPortalRef] = useState(null); - - return ( - -
setPortalRef(el)} - style={{ height: '100px', width: '100px', border: '2px solid black' }} - > -
-
- - - ); -} -``` - - - -## Angular - -We can use a basic service to share our instance of a `Portal` between multiple components, parent and child alike. - -```typescript -import { Portal, PortalModule, TemplatePortal } from '@angular/cdk/portal'; - -@Injectable({ - providedIn: 'root', -}) -class PortalService { - portal: Portal | null = null; -} - -@Component({ - selector: 'modal', - template: ` - Test - `, -}) -class ModalComponent implements OnDestroy { - @ViewChild('portalContent') portalContent: TemplateRef; - - viewContainerRef = inject(ViewContainerRef); - domPortal: TemplatePortal; - - portalService = inject(PortalService); - - ngAfterViewInit() { - // This is to avoid an: - // "Expression has changed after it was checked" - // error when trying to set domPortal - setTimeout(() => { - this.portalService.portal = new TemplatePortal( - this.portalContent, - this.viewContainerRef - ); - }); - } - - ngOnDestroy() { - this.portalService = null; - } -} - -@Component({ - selector: 'my-app', - template: ` -
- -
- - `, -}) -class AppComponent { - portalService = inject(PortalService); -} -``` - - - - - -## Vue - -``` - - - - -``` - - - - - -// TODO: Write this - - - -# HTML-Wide Portals - -// TODO: Write - - - -## React - -// TODO: Write - -Alternatively, `ReactDOM.createPortal` supports passing an arbitrary HTML DOM node, such as `html.body`: - -```jsx -import React, { useMemo } from 'react'; -import ReactDOM from 'react-dom'; - -function ChildComponent() { - const bodyEl = useMemo(() => { - return document.querySelector('body'); - }, []); - return ReactDOM.createPortal(
Hello, world!
, bodyEl); -} - -export default function App() { - return ; -} -``` - -## Angular - -// TODO: Write - -Can't do this - -## Vue - -// TODO: Write - -```vue - - - - -``` - -```vue - - - - -``` - - - -