角度窗口调整大小事件

Posted

技术标签:

【中文标题】角度窗口调整大小事件【英文标题】: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 事件事物。我在这里为自己攀登了一条非常陡峭的学习曲线,感谢您的意见。 【参考方案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 Queries 的ResizeSensor

示例用法

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 但这似乎行不通。服务中的过滤器似乎很奇怪***.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 是其使用方式的参考

以上是关于角度窗口调整大小事件的主要内容,如果未能解决你的问题,请参考以下文章

在窗口调整大小事件

在窗口调整大小时调整图像地图大小

如何使用子窗口中的句柄获取父窗口的调整大小事件?

检测何时使用 JavaScript 调整窗口大小?

在 SDL 2 中获取连续的窗口调整大小事件

主干视图的窗口调整大小事件