import { Component, OnInit, ElementRef, ViewEncapsulation, Renderer, Renderer2 } from '@angular/core'; @Component({ selector: '[mat-image-viewer]', encapsulation: ViewEncapsulation.None, template: ``, styleUrls: ['./mat-image-viewer.component.less'] }) export class MatImageViewerComponent implements OnInit { target = null; scrollTop = 0; isAnimating = false; overlay: any; options = { background: 'rgba(0,0,0,.7)', margin: 0 }; constructor( private _el: ElementRef, private _renderer2: Renderer2) { this.overlay = this.createOverlay(); } createOverlay() { const overlay = document.createElement('div'); overlay.classList.add('medium-zoom-overlay'); overlay.style.backgroundColor = this.options.background; return overlay; } onClick(event: any) { event.preventDefault(); this.triggerZoom(event); } animateTarget() { if (!this.target) return; const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; const viewportWidth = windowWidth - (this.options.margin * 2); const viewportHeight = windowHeight - (this.options.margin * 2); const { width, height, naturalWidth = +Infinity, naturalHeight = +Infinity } = this.target; const { top, left } = this.target.getBoundingClientRect(); const isCenterAligned = Math.abs((windowWidth / 2) - (left + (width / 2))) <= 10; const scaleX = Math.min(naturalWidth, viewportWidth) / width; const scaleY = Math.min(naturalHeight, viewportHeight) / height; const scale = Math.min(scaleX, scaleY) || 1; const translateX = isCenterAligned ? 0 : (-left + ((viewportWidth - width) / 2)) / scale; const translateY = (-top + ((viewportHeight - height) / 2) + this.options.margin) / scale; this.target.style.transform = `scale(${scale}) translate3d(${translateX}px, ${translateY}px, 0)`; } zoom() { if (!this.target) return; const event = new Event('show'); this.target.dispatchEvent(event); this.scrollTop = document.body.scrollTop; this.isAnimating = true; document.body.appendChild(this.overlay); requestAnimationFrame(() => { document.body.classList.add('medium-zoom--open'); }); this.target.classList.add('medium-zoom-image--open'); // this.target.addEventListener('transitionend', this.onZoomEnd.bind(this)); this.animateTarget(); } zoomOut() { if (!this.target) return; const event = new Event('hide'); this.target.dispatchEvent(event); setTimeout(() => { this.isAnimating = true; document.body.classList.remove('medium-zoom--open'); this.target.style.transform = 'none'; this.onZoomOutEnd(); // removeEventListener 不可用,zone判定不是一个函数 // this.target.addEventListener('transitionend', this.onZoomOutEnd.bind(this)); }, 150); } onZoomEnd() { this.isAnimating = false; // this.target.removeEventListener('transitionend', this.onZoomEnd.bind(this)); const event = new Event('shown'); this.target.dispatchEvent(event); } onZoomOutEnd() { if (!this.target) return; document.body.removeChild(this.overlay); this.target.classList.remove('medium-zoom-image--open'); this.isAnimating = false; // this.target.removeEventListener('transitionend', this.onZoomOutEnd.bind(this)); const event = new Event('hidden'); this.target.dispatchEvent(event); this.target = null; } triggerZoom(event: any) { if (!this.target) { this.target = event.target; this.zoom(); } else { this.zoomOut(); } } ngOnInit() { this.overlay.addEventListener('click', this.zoomOut.bind(this)); this._el.nativeElement.addEventListener('click', this.onClick.bind(this)); this._el.nativeElement.style.transition = 'transform .3s cubic-bezier(.2,0,.2,1), -webkit-transform .3s cubic-bezier(.2,0,.2,1)'; } }