代码之家  ›  专栏  ›  技术社区  ›  Optiq

我们如何在shadowDOM中加载外部样式表?

  •  0
  • Optiq  · 技术社区  · 11 月前

    我用Angular 16为我为响应式设计创建的一个新的CSS实用程序构建了一个文档网站。为了演示响应式功能,我创建了一个具有可调整大小的组件 <iframe> 我将演示组件嵌入其中。我这样做是为了利用 <iframe> 的独立DOM实例,因此演示会响应 <iframe> 而不必调整整个浏览器窗口的大小,这对用户来说很方便。

    我有两个样式表需要使用,一个用于CSS实用程序,另一个用于所有颜色定义。我唯一能让它们工作的方法就是把它们导入主 styles.css 我的角项目,并将其附加到头部 <iframe> .

    我的组件的代码看起来像这样,减去了一些与问题无关的东西。

    @Component({
      selector        : 'responsive-window',
      standalone      : true,
      imports         : [ CommonModule ],
      templateUrl     : './responsive-window.component.html',
      styleUrls       : ['./responsive-window.component.css'],
      changeDetection : ChangeDetectionStrategy.OnPush
    })
    
    export class ResponsiveWindowComponent implements OnInit, AfterViewInit, AfterContentChecked, OnDestroy {
        
        /* I pass the components I want to embed through this Input */
        @Input() Demonstration! : Type<Component>;
    
        /* I use this to target the iframe in the template */
        @ViewChild('domFrame', {static: true, read: ElementRef}) DomFrame! : ElementRef;
    
        constructor(
            private cdr   : ChangeDetectorRef,
            private vcRef : ViewContainerRef,
            private zone  : NgZone
        ){}
    
        ngAfterViewInit(): void {
    
            /* embeds the component into the iframe after the view is loaded */
            this.embedContent();
    
        }
    
        public embedContent(): void{
          /* targets the iframe */
          const frame = this.DomFrame?.nativeElement.contentDocument || this.DomFrame?.nativeElement.contentWindow;
    
          /* resolves the component */
          const item : ComponentRef<any> = this.vcRef.createComponent<Component>(this.Demonstration);
    
          /* for setting up a broadcast channel to send data back and forth from outside the iframe */
          if(reflectComponentType(this.Demonstration)?.inputs.find(a=> a.propName === 'BroadcastName') !== undefined){
            item.instance.BroadcastName = this.BroadcastName;
            item.instance.ChannelName   = this.ChannelName;
            item.instance.TargetName    = this.TargetName;
          }
    
          /* for the default styling of the iframe's body element */
          const defaultStyles = document.createElement('style');
         
          /* for attaching the main stylesheet of the angular app */
          const stylesLink     = document.createElement('link');
    
          defaultStyles.innerText = `*{ padding : 0; margin : 0; box-sizing: border-box; overflow: hidden; } body{ display : grid; place-items: center; min-height: 0px; max-height: 100vh; grid-template-columns: 1fr; grid-template-rows: 1fr; background-color: hsl(var(--gray-100), 1); }`;
        
          stylesLink.rel = 'stylesheet';
          stylesLink.type = 'text/css';
          stylesLink.href = 'styles.css';
    
          /* embedding everything into the iframe */
          frame.head.appendChild(defaultStyles);
          frame.head.appendChild(stylesLink);
          frame.body.appendChild(item.location.nativeElement);
        }
    }
    

    至于我传递到这个组件中要嵌入的组件 <iframe> ,这是一个如何设置它们的示例。

    @Component({
      selector      : 'app-example',
      standalone    : true,
      imports       : [CommonModule],
      templateUrl   : './example.component.html',
      styleUrls     : [ './example.component.css' ],
      encapsulation : ViewEncapsulation.ShadowDom
    })
    export class ExampleComponent {
    
    }
    

    我必须将封装设置为 ShadowDom 否则,组件的任何样式都不会生效,也不会从中读取任何样式 样式.css 文件嵌入到 <head> .跑步时 ng serve 一切都很完美,但当我跑步时 ng-build 并将文件加载到我的 c-panel 然后去网站,没有任何来自 样式.css 文件对嵌入到中的组件生效 <iframe> .

    我在嵌入式组件的样式表中更改了一些样式,只是为了看看它们是否会生效,它们确实生效了,这意味着它可以识别组件样式表中的样式,但当查看来自我的样式表的一些定义,包括所有颜色和CSS实用程序(它们都是CSS变量)时,它们都没有被定义。

    我试图将这些样式表导入到组件的样式表中,但没有成功。然后我尝试将它们导入元数据中,如下所示。

    @Component({
      selector: 'app-product',
      standalone: true,
      imports: [CommonModule],
      templateUrl: './product.component.html',
      styleUrls: [
        '../../../../assets/utility-styles.css',
        '../../../../assets/color-defs.css',
        './product.component.css'
    ],
      encapsulation: ViewEncapsulation.ShadowDom
    })
    

    我确保在copmonent的样式表之前先导入其他样式表,这样所有的值都会在使用之前定义好。我跑了 ng build 再次将文件加载到我的 c面板 但这也不起作用。我还将这些额外的样式表添加到 styles 数组在我的 angular.json 文件,看看这是否允许我将它们导入嵌入式组件,但也无法正常工作。

    在the responsive-window 您会注意到,我的代码中有一部分引用了 BroadcastName , ChannelName TargetName 输入,用于设置广播频道,以便用户可以尝试不同的设置,这些设置将从外部的仪表板更改嵌入式组件中某些CSS属性的值 <iframe> 。我还不知道这是否也有影响,但我很担心。有人知道我该怎么解决这个问题吗?

    1 回复  |  直到 11 月前
        1
  •  0
  •   Ricudo    11 月前

    构建项目后,这两个网址将无法使用。

    '../../../../assets/utility-styles.css',
    '../../../../assets/color-defs.css',
    

    因为资产相对于部署的应用程序的根URL具有不同的路径。您应该使用:

    'assets/utility-styles.css',
    'assets/color-defs.css',