【问题标题】:Angular window resize event角度窗口调整大小事件
【发布时间】:2016-06-02 08:00:33
【问题描述】:

我想根据窗口调整大小事件(加载时和动态)执行一些任务。

目前我的 DOM 如下:

<div id="Harbour">
    <div id="Port" (window:resize)="onResize($event)" >
        <router-outlet></router-outlet>
    </div>
</div>

事件正确触发

export class AppComponent {
    onResize(event) {
        console.log(event);
    }
}

如何从这个事件对象中获取宽度和高度?

谢谢。

【问题讨论】:

  • 不是一个真正的 Angular 问题。查看window 对象。你可以得到它的innerHeightinnerWidth 属性..
  • @Sasxa 是正确的,你只需要做console.log(event.target.innerWidth )
  • 感谢 Sasxa/Pankaj 提供的信息 - 我不确定它是否只是普通的 javascript 事物、Typescript 事物或 Angular 事件事物。我在这里为自己攀登了一条非常陡峭的学习曲线,感谢您的意见。

标签: javascript angular


【解决方案1】:

在 Angular CDK 中有一个 ViewportRuler 服务。它在区域外运行,支持 orientationchangeresize。它也适用于服务器端渲染。

@Component({
  selector: 'my-app',
  template: `
    <p>Viewport size: {{ width }} x {{ height }}</p>
  `
})
export class AppComponent implements OnDestroy {
  width: number;
  height: number;
  private readonly viewportChange = this.viewportRuler
    .change(200)
    .subscribe(() => this.ngZone.run(() => this.setSize()));
  constructor(
    private readonly viewportRuler: ViewportRuler,
    private readonly ngZone: NgZone
  ) {
    // Change happens well, on change. The first load is not a change, so we init the values here. (You can use `startWith` operator too.)
    this.setSize();
  }
  // Never forget to unsubscribe!
  ngOnDestroy() {
    this.viewportChange.unsubscribe();
  }

  private setSize() {
    const { width, height } = this.viewportRuler.getViewportSize();
    this.width = width;
    this.height = height;
  }
}

Stackblitz example for ViewportRuler

好处是,它限制了更改检测周期(它只会在您在区域中运行回调时触发),而(window:resize) 将在每次调用时触发更改检测。

【讨论】:

  • 这应该是公认的答案。拥有所需的一切 + 这是 Angular CDK 内置功能。
  • 这个答案需要更多的支持。在性能和兼容性方面,这是最好的解决方案
  • 试过这个并让它工作....性能比上面的选项慢得多。
  • @pistol-pete 我添加了一个示例来展示它的高性能。
【解决方案2】:

我采取的另一种方法是

import {Component, OnInit} from '@angular/core';
import {fromEvent} from "rxjs";
import {debounceTime, map, startWith} from "rxjs/operators";


function windowSizeObserver(dTime = 300) {
  return fromEvent(window, 'resize').pipe(
    debounceTime(dTime),
    map(event => {
      const window = event.target as Window;

      return {width: window.innerWidth, height: window.innerHeight}
    }),
    startWith({width: window.innerWidth, height: window.innerHeight})
  );
}

@Component({
  selector: 'app-root',
  template: `
    <h2>Window Size</h2>
    <div>
      <span>Height: {{(windowSize$ | async)?.height}}</span>
      <span>Width: {{(windowSize$ | async)?.width}}</span>
    </div>
  `
})
export class WindowSizeTestComponent {
  windowSize$ = windowSizeObserver();
}

这里的windowSizeObserver 可以在任何组件中重复使用

【讨论】:

    【解决方案3】:

    我的做法如下,很像 Johannes Hoppe 建议的:

    import { EventManager } from '@angular/platform-browser';
    import { Injectable, EventEmitter } from '@angular/core';
    
    @Injectable()
    export class ResizeService {
    
      public onResize$ = new EventEmitter<{ width: number; height: number; }>();
    
      constructor(eventManager: EventManager) {
        eventManager.addGlobalEventListener('window', 'resize',
          event => this.onResize$.emit({
            width: event.target.innerWidth,
            height: event.target.innerHeight
          }));
      }
      
      getWindowSize(){
        this.onResize$.emit({
        width: window.innerWidth,
        height: window.innerHeight
        });
      }
    }

    在 app.component.ts 中:

    Import { ResizeService } from ".shared/services/resize.service"
    import { Component } from "@angular/core"
    
    @Component({
      selector: "app-root",
      templateUrl: "./app.component.html",
      styleUrls: ["./app.component.css"]
    })
    export class AppComponent{
      windowSize: {width: number, height: number};
      
      constructor(private resizeService: ResizeService){
      }
    
      ngOnInit(){
        this.resizeService.onResize$.subscribe((value) => {
          this.windowSize = value;
        });
        
        this.resizeService.getWindowSize();
      }
    }

    然后在你的 app.component.html 中:

    <router-outlet *ngIf = "windowSize?.width > 1280 && windowSize?.height > 700; else errorComponent">
    </router-outlet>
    
    <ng-template #errorComponent>
      <app-error-component></app-error-component>
    </ng-template>

    【讨论】:

      【解决方案4】:

      这是我创建的一个简单而干净的解决方案,因此我可以将它注入到多个组件中。

      ResizeService.ts

      import { Injectable } from '@angular/core';
      import { Subject } from 'rxjs';
      
      @Injectable({
        providedIn: 'root'
      })
      export class ResizeService {
      
        constructor() {
      
          window.addEventListener('resize', (e) => {
            this.onResize.next();
          });
      
        }
      
        public onResize = new Subject();
      
      }
      

      使用中:

      constructor(
        private resizeService: ResizeService
      ) { 
      
        this.subscriptions.push(this.resizeService.onResize.subscribe(() => {
          // Do stuff
        }));
      
      }
      
      private subscriptions: Subscription[] = [];
      

      【讨论】:

        【解决方案5】:

        这是使用最新版本的 Rxjs 对上述@GiridharKamik 答案的更新。

        import { Injectable } from '@angular/core';
        import { Observable, BehaviorSubject, fromEvent } from 'rxjs';
        import { pluck, distinctUntilChanged, map } from 'rxjs/operators';
        
        @Injectable()
        export class WindowService {
            height$: Observable<number>;
            constructor() {
                const windowSize$ = new BehaviorSubject(getWindowSize());
        
                this.height$ = windowSize$.pipe(pluck('height'), distinctUntilChanged());
        
                fromEvent(window, 'resize').pipe(map(getWindowSize))
                    .subscribe(windowSize$);
            }
        
        }
        
        function getWindowSize() {
            return {
                height: window.innerHeight
                //you can sense other parameters here
            };
        };
        

        【讨论】:

          【解决方案6】:

          我检查了大部分答案。然后决定查看 Layout 上的 Angular 文档。

          Angular 有自己的 Observer 用于检测不同的大小,并且很容易实现到组件或服务中。

          一个简单的例子是:

          import {BreakpointObserver, Breakpoints} from '@angular/cdk/layout';
          
          @Component({...})
          class MyComponent {
            constructor(breakpointObserver: BreakpointObserver) {
              breakpointObserver.observe([
                Breakpoints.HandsetLandscape,
                Breakpoints.HandsetPortrait
              ]).subscribe(result => {
                if (result.matches) {
                  this.activateHandsetLayout();
                }
              });
            }
          }

          希望对你有帮助

          【讨论】:

          • 很高兴知道...但请注意...这是材料库 Angular 文档,而不是 Angular 本身的官方文档。有点暗示所有断点都将遵循材料指南 OOTB。
          【解决方案7】:

          所有解决方案都不适用于服务器端或角度通用

          【讨论】:

            【解决方案8】:
            <div (window:resize)="onResize($event)"
            
            onResize(event) {
              event.target.innerWidth;
            }
            

            或使用HostListener decorator:

            @HostListener('window:resize', ['$event'])
            onResize(event) {
              event.target.innerWidth;
            }
            

            支持的全局目标是 windowdocumentbody

            在 Angular 中实现 https://github.com/angular/angular/issues/13248 之前,强制订阅 DOM 事件并使用 RXJS 减少事件数量会更好,如其他一些答案所示。

            【讨论】:

            • 是否有任何关于您使用的语法的文档:window:resize
            • 没错。您可以使用documentwindowbody github.com/angular/angular/blob/…
            • 完美答案.. 我相信@HostListener 是更干净的方式:) 但请确保首先使用import { Component, OnInit, HostListener } from '@angular/core'; 导入HostListener
            • 快速提示:如果也想在首次加载时触发,请从 @angular/core 实现 ngAfterViewInit。 angular.io/api/core/AfterViewInit
            • 当然,但是,对于那些想知道 HostListener 如何与 debounceTime 一起工作的人,请点击下面的链接plnkr.co/edit/3J0dcDaLTJBxkzo8Akyg?p=preview
            【解决方案9】:

            我知道很久以前有人问过这个问题,但现在有更好的方法来做到这一点!我不确定是否有人会看到这个答案。显然你的进口:

            import { fromEvent, Observable, Subscription } from "rxjs";
            

            然后在你的组件中:

            resizeObservable$: Observable<Event>
            resizeSubscription$: Subscription
            
            ngOnInit() {
                this.resizeObservable$ = fromEvent(window, 'resize')
                this.resizeSubscription$ = this.resizeObservable$.subscribe( evt => {
                  console.log('event: ', evt)
                })
            }
            

            那么一定要在销毁时取消订阅!

            ngOnDestroy() {
                this.resizeSubscription$.unsubscribe()
            }
            

            【讨论】:

            • 唯一对我有用的方法。谢谢!! :-) ...我只需要调整您的导入。也许我的 rxjs 更新:import { fromEvent, Observable,Subscription } from "rxjs";
            • 在哪里可以添加 debounce(1000)?
            • 抱歉回复晚了:要添加去抖动功能,您需要使用this.resizeSubscription$ = this.resizeObservable$.pipe(debounceTime(1000)).subscribe( evt =&gt; { console.log('event: ', evt) })
            • 对于去抖动,从 'rxjs/operators' 导入 { debounceTime };
            • 尽管使用debounceTime 并减少了订阅回调,Angular 仍将对每个事件运行更改检测,因为它是在区域内触发的。您应该检查我的答案以消除此问题。
            【解决方案10】:

            下面的代码让我们观察 Angular 中任何给定 div 的任何大小变化。

            <div #observed-div>
            </div>
            

            然后在组件中:

            oldWidth = 0;
            oldHeight = 0;
            
            @ViewChild('observed-div') myDiv: ElementRef;
            ngAfterViewChecked() {
              const newWidth = this.myDiv.nativeElement.offsetWidth;
              const newHeight = this.myDiv.nativeElement.offsetHeight;
              if (this.oldWidth !== newWidth || this.oldHeight !== newHeight)
                console.log('resized!');
            
              this.oldWidth = newWidth;
              this.oldHeight = newHeight;
            }
            

            【讨论】:

            • 我想在最大宽度 767px 处将一个变量从 counter = 6 更改为 count = 0,我该怎么做?
            【解决方案11】:

            我还没有看到有人谈论 MediaMatcherangular/cdk

            您可以定义一个 MediaQuery 并将一个侦听器附加到它 - 然后如果匹配器匹配,您可以在模板(或 ts)上的任何位置调用东西。 LiveExample

            App.Component.ts

            import {Component, ChangeDetectorRef} from '@angular/core';
            import {MediaMatcher} from '@angular/cdk/layout';
            
            @Component({
              selector: 'app-root',
              templateUrl: './app.component.html',
              styleUrls: ['./app.component.css']
            })
            export class AppComponent {
              mobileQuery: MediaQueryList;
            
              constructor(changeDetectorRef: ChangeDetectorRef, media: MediaMatcher) {
                this.mobileQuery = media.matchMedia('(max-width: 600px)');
                this._mobileQueryListener = () => changeDetectorRef.detectChanges();
                this.mobileQuery.addListener(this._mobileQueryListener);
              }
            
              private _mobileQueryListener: () => void;
            
              ngOnDestroy() {
                this.mobileQuery.removeListener(this._mobileQueryListener);
              }
            
            }
            

            App.Component.Html

            <div [class]="mobileQuery.matches ? 'text-red' : 'text-blue'"> I turn red on mobile mode 
            </div>
            

            App.Component.css

            .text-red { 
               color: red;
            }
            
            .text-blue {
               color: blue;
            }
            

            来源:https://material.angular.io/components/sidenav/overview

            【讨论】:

              【解决方案12】:

              这不是问题的确切答案,但它可以帮助需要检测任何元素大小变化的人。

              我创建了一个库,可以将resized 事件添加到任何元素 - Angular Resize Event

              它在内部使用来自CSS Element QueriesResizeSensor

              示例用法

              HTML

              <div (resized)="onResized($event)"></div>
              

              打字稿

              @Component({...})
              class MyComponent {
                width: number;
                height: number;
              
                onResized(event: ResizedEvent): void {
                  this.width = event.newWidth;
                  this.height = event.newHeight;
                }
              }
              

              【讨论】:

              • 如果你想检测窗口调整大小,那么 Angular Material Breakpoint Observer 已经处理 material.angular.io/cdk/layout/overview
              • 但这并不仅仅检测窗口调整大小,而是检测任何元素调整大小。
              【解决方案13】:

              基于@cgatian 的解决方案,我建议进行以下简化:

              import { EventManager } from '@angular/platform-browser';
              import { Injectable, EventEmitter } from '@angular/core';
              
              @Injectable()
              export class ResizeService {
              
                public onResize$ = new EventEmitter<{ width: number; height: number; }>();
              
                constructor(eventManager: EventManager) {
                  eventManager.addGlobalEventListener('window', 'resize',
                    e => this.onResize$.emit({
                      width: e.target.innerWidth,
                      height: e.target.innerHeight
                    }));
                }
              }
              

              用法:

              import { Component } from '@angular/core';
              import { ResizeService } from './resize-service';
              
              @Component({
                selector: 'my-component',
                template: `{{ rs.onResize$ | async | json }}`
              })
              export class MyComponent {
                constructor(private rs: ResizeService) { }
              }
              

              【讨论】:

              • 我发现这是最好的解决方案,但是,这仅在调整窗口大小时有效,而在加载或路由器更改时无效。你知道,如何申请路由器更换、重新加载或加载吗?
              • 可以在服务中添加一个函数并在组件@jcdsr中触发:getScreenSize() { this.onResize$.emit({ width: window.innerWidth, height: window.innerHeight }); }
              【解决方案14】:

              这样做的正确方法是利用EventManager 类来绑定事件。这允许您的代码在其他平台上工作,例如使用 Angular Universal 进行服务器端渲染。

              import { EventManager } from '@angular/platform-browser';
              import { Observable } from 'rxjs/Observable';
              import { Subject } from 'rxjs/Subject';
              import { Injectable } from '@angular/core';
              
              @Injectable()
              export class ResizeService {
              
                get onResize$(): Observable<Window> {
                  return this.resizeSubject.asObservable();
                }
              
                private resizeSubject: Subject<Window>;
              
                constructor(private eventManager: EventManager) {
                  this.resizeSubject = new Subject();
                  this.eventManager.addGlobalEventListener('window', 'resize', this.onResize.bind(this));
                }
              
                private onResize(event: UIEvent) {
                  this.resizeSubject.next(<Window>event.target);
                }
              }
              

              在组件中的使用很简单,只需将此服务作为提供程序添加到您的 app.module 中,然后将其导入组件的构造函数中。

              import { Component, OnInit } from '@angular/core';
              
              @Component({
                selector: 'my-component',
                template: ``,
                styles: [``]
              })
              export class MyComponent implements OnInit {
              
                private resizeSubscription: Subscription;
              
                constructor(private resizeService: ResizeService) { }
              
                ngOnInit() {
                  this.resizeSubscription = this.resizeService.onResize$
                    .subscribe(size => console.log(size));
                }
              
                ngOnDestroy() {
                  if (this.resizeSubscription) {
                    this.resizeSubscription.unsubscribe();
                  }
                }
              }
              

              【讨论】:

              • 据我所知,这不会在移动设备上触发。您如何使用这种方法获得初始窗口大小?
              • 移动设备没有window 对象,就像服务器没有window 一样。我不熟悉不同的移动配置,但您应该能够轻松调整上述代码以绑定到正确的全局事件侦听器
              • @cgatian 我是菜鸟,但这似乎是正确的答案。不幸的是,我没有运气让我的订阅登录组件。您能否在答案中添加如何在组件中订阅此内容以便可以看到更新?
              • @cgatian 我会做一个 plunker 但这似乎行不通。服务中的过滤器似乎很奇怪stackoverflow.com/q/46397531/1191635
              • @cgatian Mobile does not have a window object.... 为什么你认为移动浏览器没有窗口对象?
              【解决方案15】:

              我写了this lib 来查找 Angular 中的组件边界大小更改(调整大小),这可能对其他人有所帮助。你可以把它放在根组件上,和调整窗口大小一样。

              第一步:导入模块

              import { BoundSensorModule } from 'angular-bound-sensor';
              
              @NgModule({
                (...)
                imports: [
                  BoundSensorModule,
                ],
              })
              export class AppModule { }
              

              第 2 步:添加如下指令

              &lt;simple-component boundSensor&gt;&lt;/simple-component&gt;

              第 3 步:接收边界尺寸详细信息

              import { HostListener } from '@angular/core';
              
              @Component({
                selector: 'simple-component'
                (...)
              })
              class SimpleComponent {
                @HostListener('resize', ['$event'])
                onResize(event) {
                  console.log(event.detail);
                }
              }
              

              【讨论】:

                【解决方案16】:

                假设

                首先我们需要当前的窗口大小。所以我们创建了一个 observable,它只发出一个值:当前窗口大小。

                initial$ = Observable.of(window.innerWidth > 599 ? false : true);
                

                然后我们需要创建另一个 observable,以便我们知道窗口大小何时改变。为此,我们可以使用“fromEvent”运算符。想了解更多关于 rxjs 的操作符请访问:rxjs

                resize$ = Observable.fromEvent(window, 'resize').map((event: any) => {
                  return event.target.innerWidth > 599 ? false : true;
                 });
                

                合并这两个流以接收我们的 observable:

                mobile$ = Observable.merge(this.resize$, this.initial$).distinctUntilChanged();
                

                现在您可以像这样订阅它:

                mobile$.subscribe((event) => { console.log(event); });
                

                记得退订:)

                【讨论】:

                  【解决方案17】:

                  在 Angular2 (2.1.0) 上,我使用 ngZone 来捕获屏幕更改事件。

                  看一下例子:

                  import { Component, NgZone } from '@angular/core';//import ngZone library
                  ...
                  //capture screen changed inside constructor
                  constructor(private ngZone: NgZone) {
                      window.onresize = (e) =>
                      {
                          ngZone.run(() => {
                              console.log(window.innerWidth);
                              console.log(window.innerHeight);
                          });
                      };
                  }
                  

                  希望对你有所帮助!

                  【讨论】:

                    【解决方案18】:

                    这是一个更好的方法。基于Birowsky's 的回答。

                    第 1 步:使用 RxJS Observables 创建一个 angular service

                    import { Injectable } from '@angular/core';
                    import { Observable, BehaviorSubject } from 'rxjs';
                    
                    @Injectable()
                    export class WindowService {
                        height$: Observable<number>;
                        //create more Observables as and when needed for various properties
                        hello: string = "Hello";
                        constructor() {
                            let windowSize$ = new BehaviorSubject(getWindowSize());
                    
                            this.height$ = (windowSize$.pluck('height') as Observable<number>).distinctUntilChanged();
                    
                            Observable.fromEvent(window, 'resize')
                                .map(getWindowSize)
                                .subscribe(windowSize$);
                        }
                    
                    }
                    
                    function getWindowSize() {
                        return {
                            height: window.innerHeight
                            //you can sense other parameters here
                        };
                    };
                    

                    第 2 步:注入上述service 并订阅在服务中创建的任何Observables,只要您想接收窗口调整大小事件。

                    import { Component } from '@angular/core';
                    //import service
                    import { WindowService } from '../Services/window.service';
                    
                    @Component({
                        selector: 'pm-app',
                        templateUrl: './componentTemplates/app.component.html',
                        providers: [WindowService]
                    })
                    export class AppComponent { 
                    
                        constructor(private windowService: WindowService) {
                    
                            //subscribe to the window resize event
                            windowService.height$.subscribe((value:any) => {
                                //Do whatever you want with the value.
                                //You can also subscribe to other observables of the service
                            });
                        }
                    
                    }
                    

                    对反应式编程有充分的理解总是有助于克服困难的问题。希望这对某人有所帮助。

                    【讨论】:

                    • 我相信这是一个错误:this.height$ = (windowSize$.pluck('height') as Observable).distinctUntilChanged();Observable).distinctUntilChanged() ;看起来你连续两次粘贴到 distinctUntilChanged() 中
                    • 我没听懂你,请详细说明。
                    • 更改检测不会被触发,因为您在 Angular 之外执行此事件,相信这就是他的意思。
                    • 我有一个错误说“pluck”在 BehaviorSubject 类型上不存在。将代码更改为 this.height$ = windowSize$.map(x => x.height) 对我有用。
                    • @GiridharKamik 你能提供一个同时订阅宽度和高度的解决方案吗,我们可以在一个简单的订阅中同时订阅宽度和高度
                    【解决方案19】:

                    @Günter 的回答是正确的。我只是想提出另一种方法。

                    您还可以在@Component()-decorator 中添加主机绑定。您可以将事件和所需的函数调用放在 host-metadata-property 中,如下所示:

                    @Component({
                      selector: 'app-root',
                      templateUrl: './app.component.html',
                      styleUrls: ['./app.component.css'],
                      host: {
                        '(window:resize)': 'onResize($event)'
                      }
                    })
                    export class AppComponent{
                       onResize(event){
                         event.target.innerWidth; // window width
                       }
                    }
                    

                    【讨论】:

                    • 我已经尝试了此页面上的所有方法,但似乎它们都不起作用。有没有这方面的官方文档。
                    • 加载时如何获取视口高度?
                    • @GiridharKarnik,您是否尝试过在ngOnInitngAfterViewInit 方法中执行window.innerWidth
                    • @Tomato API 参考可以在here 找到很多参考是自动生成的(我想),并且缺少示例或详细说明。一些 api-references 有很多例子。不过,我无法从文档中找到关于此的具体示例。也许它隐藏在某个地方:P
                    • here 是其使用方式的参考
                    猜你喜欢
                    • 2014-04-06
                    • 1970-01-01
                    • 1970-01-01
                    • 2010-10-13
                    • 2017-04-26
                    • 2018-12-13
                    相关资源
                    最近更新 更多