2024开门第一天,研究一下shadow

一直都是听说,今天打算研究下,我把学习记录一下!

shadow介绍

shadow我的理解其实就是在真实dom下添加一个隔离区,这个隔离区内部可以写html css js,它的能力就是一个隔离作用。

官方解释说shadow是一个影子dom,允许你将一个 DOM 树附加到一个元素上,并且使该树的内部对于在页面中运行的 JavaScript 和 CSS 是隐藏的。

这里以一张图为为例,比如html5里input 标签可以设置type参数range、 date、 time,本质也是基于shadow实现的。

input标签默认是看不到的,这个需要在浏览器上去设置一个属性,打开控制面板这里,勾选shadowDOM选项

shadow组成部分

这个隔离区它有几部分组成,影子宿主(Shadow host) 影子树(Shadow tree) 影子边界(Shadow boundary) 影子根(Shadow root)

DOM 术语:

  • 影子宿主(Shadow host) : 影子 DOM 附加到的常规 DOM 节点。
  • 影子树(Shadow tree) : 影子 DOM 内部的 DOM 树。
  • 影子边界(Shadow boundary) : 影子 DOM 终止,常规 DOM 开始的地方。
  • 影子根(Shadow root) : 影子树的根节点。

可以参考这张图理解

shadow隔离区元素如何修改

这里以这种图为例

可以在控制面板上直接通过js方式来获取和修改,这个标签是我们自定义的,可以直接来修改。

如果是原生的input video等标签是无法通过这种方式修改的。

如何创建一个shadow

这里我以react中项目使用为例,通过window.customElements.define api来创建一个自定义的 组件。

js 复制代码
import React from "react";
import ReactDOM from "react-dom";
import {useSetState} from 'ahooks';

window.customElements.define('custom-card',class extends HTMLElement {
  constructor() {
    super()

    this.shadow = this.attachShadow({mode:'open'})
  }

  connectedCallback () {
    this.template = document.createElement('template')
    this.template.innerHTML = `
     <div class="card-list">
      <div class="card-title">${this.dataset.title}</div>
      <div class="card-content">${this.dataset.content}</div>
     </div>
    `

    this.styles = document.createElement('style');
    this.styles.textContent = `
      .card-list {
        border:1px solid #ccc;
        padding:12px 16px;
      }

      .card-title {
        font-size:14px;
        color:#999;
      }

      .card-content {
        font-size:18px;
        color:#333;
      }
    `;
    
    this.shadow.appendChild(this.styles)
    this.shadow.appendChild(this.template.content)
  }
})

function App() {
  const [state,setState] = useSetState({
    title: '这是标题',
    content: '这是内容',
  })

  return <div style={{margin:'50px auto',width:'100%',maxWidth:600}}>
    <custom-card data-title={state.title} data-content={state.content}/>
  </div>
}

document.body.insertAdjacentHTML("afterbegin", `<div id="root"></div>`);

ReactDOM.render(
  <>
    <React.Suspense fallback="">
      <App />
    </React.Suspense>
  </>,
  document.querySelector("#root")
);

shadow知识点梳理

1.利用 this.attachShadow({mode:'open'}) 隔离特性 mode: 'open' | 'close' 设置为close外交将无法访问,上面说的input video等标签原理也是通过这个属性设置的。
2.customElements本身也是带有生命周期,两个比较常用,connectedCallback(初始化执行 ) attributeChangedCallback(属性变化更新)
3.属性传值 customElements定义标签可以通过外部data-title data-xxx的方式来传参 它的内部没有vue的响应数据自动更新,需要借助attributeChangedCallback属性监听属性变化来更新dom或样式

具体代码如如下

jsx 复制代码
import React from "react";
import ReactDOM from "react-dom";
import {useSetState} from 'ahooks';
import mockjs from 'mockjs';

window.customElements.define('custom-card',class extends HTMLElement {
  constructor() {
    super()

    this.shadow = this.attachShadow({mode:'open'})
  }

  static get observedAttributes () {
    return ['data-title','data-content']
  }

  attributeChangedCallback (name,oldName,newName) {
    name === 'data-title' && (this.shadow.querySelector('h6').textContent = newName);
    name === 'data-content' && (this.shadow.querySelector('h5').textContent = newName);
  }

  connectedCallback () {
    this.template = document.createElement('template')
    this.template.innerHTML = `
      <h6>${this.dataset.title}</h6>
      <h5>${this.dataset.content}</h5>
    `
    this.shadow.appendChild(this.template.content)
  }
})

function App() {
  const [state,setState] = useSetState({
    imgurl:mockjs.Random.image(),
    content:mockjs.Random.csentence(),
    title:mockjs.Random.ctitle(10),
  })

  return <div style={{margin:'50px auto',width:'100%',maxWidth:600}}>
    <custom-card data-title={state.title} data-content={state.content}/>
  </div>
}

document.body.insertAdjacentHTML("afterbegin", `<div id="root"></div>`);

ReactDOM.render(
  <>
    <React.Suspense fallback="">
      <App />
    </React.Suspense>
  </>,
  document.querySelector("#root")
);

4.被设置shadow属性里面部分标签也会继承外部标签的属性,比如文字样式特性。

以下面的两段代码为例

  1. shadow子元素可以取消继承 支持单个取消 和 全部取消

取消之后这里内容将不会继承外部的body

  1. shadow元素也是支持slot属性,这个使用和vue的插槽比较类似

定义插槽

使用插槽

完整代码如下

jsx 复制代码
import React from "react";
import ReactDOM from "react-dom";

class CustomCard extends HTMLElement {
  constructor (){
    super()
    console.log(this,'constructor')

    this.template = document.createElement('template');
    this.styles = document.createElement('style');

    // this.shadow = this
    this.shadow = this.attachShadow({mode:'open'})
  }

  connectedCallback () {
    this.render()
  }

  render () {
    this.styles.textContent = ` 
      :host {
        all: initial;
        --list-border-color: ${'#dedede'};
      }
      .card-list{
        border:1px solid var(--list-border-color);
        border-radius: 1px;
      }

      .card-item{
        padding:20px;
      }

      .card-tt{
        display:flex;
        align-items:center;
      }

      .card-td{
        color: #666;
        margin-bottom:10px;
      }

      .card-title {
        font-size:14px;
        color:#999;
        margin-left:10px;
      }

      .card-img{
        width:60px;
      }
    `
    this.template.innerHTML = `
     <div>
      <div class="card-list">
          <div class="card-item">
            <div class="card-tt">
              <img class="card-img" crossorigin="use"  src='${this.dataset.imgurl}'/>
              <span class="card-title">${this.dataset.title}</span>
            </div>
            <div class="card-td">
            ${this.dataset.content}
          </div>
          </div>
        </div>
     </div>
    `
    this.shadow.appendChild(this.template.content)
    this.shadow.appendChild(this.styles)
  }
}

window.customElements.define('custom-button',class extends HTMLElement {
  constructor () {
   super()
   this.shadow = this.attachShadow({mode:'open'})
  }

  static get observedAttributes() {
    return ["data-text"];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    console.log(name,'name')
    console.log(oldValue,'oldValue')
    console.log(newValue,'newValue')
    this.shadowRoot.querySelector('button').textContent = newValue;
    // console.log("Custom square element attributes changed.");
  }

  connectedCallback () {
    this.template = document.createElement('template');
    this.template.innerHTML = `
      <button>${this.dataset.text || ''}</button>
    `
    this.shadow.appendChild(this.template.content)
  }
})

window.customElements.define('custom-card',CustomCard)

window.customElements.define('custom-container',class extends HTMLElement {
  constructor () {
    super()
    this.shadom = this.attachShadow({mode:'open'})
  }

  connectedCallback () {
    this.template = document.createElement('template')
    this.template.innerHTML = `
      <div>
        <slot name="content1"></slot>
        <slot name="content2"></slot>
        <slot name="content3"></slot>
      </div>
    `
    this.shadom.appendChild(this.template.content)
  }
})

function App() {
  const [visible,setVisible] = React.useState(true)

  function handleClick () {
    setVisible((v) => !v)
  }

  return <div>
    <style>
      {
        `
        .card-list{
          border:1px solid #0f0 !important;
        }
        body {
          font-family:'PingFang';
          font-size: 20px;
        }
        `
      }
    </style>
    <custom-container>
       <custom-button slot="content1" data-text={visible ? '关闭' : '展开'} onClick={handleClick}/>
       {
         visible && <custom-card slot="content2" className="custom-card" data-title={'标题标题123'}
         data-imgurl="https://puui.qpic.cn/vcover_hz_pic/0/7q544xyrava3vxf1598925282532/810?max_age=7776001"
         data-content="内容内容内容内容!123" data-list-border-color="pink"/>
       }
    </custom-container>
  </div>
}

document.body.insertAdjacentHTML("afterbegin", `<div id="root"></div>`);

ReactDOM.render(
  <>
    <React.Suspense fallback="">
      <App />
    </React.Suspense>
  </>,
  document.querySelector("#root")
);

shadow使用场景

vue3官方也在推崇使用这个api的,喜欢研究的可以看下defineAsyncComponent()

微前端框架底层也是借助这个api封装实现的 无界

  1. 封装样式:使用 Shadow DOM 可以将组件的样式封装在组件内部,避免全局样式的污染和冲突。这使得组件可以具有更高的可重用性,因为它们不会受到外部样式的干扰。

  2. 隔离作用域:Shadow DOM 创建了一个与外部文档树隔离的作用域,在组件内部定义的 CSS 样式和 JavaScript 代码只会影响组件自身,不会影响其他组件或页面元素。这有助于解决命名冲突和作用域问题。

  3. 组件化开发:Shadow DOM 可以帮助开发者将组件的结构、样式和行为封装起来,使其成为一个独立的功能单元。通过这种方式,可以提高代码的模块化程度和可维护性,方便团队协作和组件复用。

  4. 私有部件:使用 Shadow DOM,可以将组件内部的某些部分标记为私有,不暴露给组件的用户。这样可以隐藏组件的实现细节,保护组件的内部结构和样式,同时提供公共接口供外部使用。

  5. 封装第三方组件:在使用第三方组件时,可以使用 Shadow DOM 来封装这些组件,以防止它们的样式和行为对整个应用程序产生意外影响。这样可以更好地控制和管理第三方组件的集成。

shadow兼容性

目前先研究到这,期待大家一块交流学习,文章只是记录一下学习过程,期待大家共同进步!

相关推荐
GIS程序媛—椰子22 分钟前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_00128 分钟前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端31 分钟前
Content Security Policy (CSP)
前端·javascript·面试
木舟100935 分钟前
ffmpeg重复回听音频流,时长叠加问题
前端
王大锤43911 小时前
golang通用后台管理系统07(后台与若依前端对接)
开发语言·前端·golang
我血条子呢1 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js
黎金安1 小时前
前端第二次作业
前端·css·css3
啦啦右一1 小时前
前端 | MYTED单篇TED词汇学习功能优化
前端·学习
半开半落1 小时前
nuxt3安装pinia报错500[vite-node] [ERR_LOAD_URL]问题解决
前端·javascript·vue.js·nuxt