Angular4+图片查看组件,支持拖动。缩放。DomHandler是一个操作dom的工具
/**
* Created by MarkBell on 2018/1/8.
*/
import {
NgModule, Component, ElementRef, OnInit, AfterViewInit, OnDestroy, Input, Output, Renderer2, Inject, forwardRef,
ViewChild, AfterViewChecked, ViewEncapsulation, NgZone
} from '@angular/core';
import {CommonModule} from '@angular/common';
import {DomHandler} from '../dom/domhandler';
import {HttpService} from "../../common/http.service";
import {Headers, ResponseContentType} from "@angular/http";
import {fromEvent} from "rxjs/observable/fromEvent";
@Component({
selector: 'ui-file-preview',
styleUrls: ['./filepreview.css'],
encapsulation: ViewEncapsulation.None,
template: `
<div #container class="filepreview-panel" (click)="filePreviewEventStopPropagation($event)" [ngClass]="styleClass"
[ngStyle]="style" [style.display]="visible ? 'block' : 'none'">
<div class="panel-container">
<div id="previewBox" #previewBox class="img-box">
<img #imgPreview class="img-preview" alt="" title="" src="{{fileUrl}}"/>
</div>
</div>
<div class="filepreview-panel-bottom flex flex-justify">
<div class="flex-left g-5">
<h5 class="font-xxl">附件预览</h5>
<p class="font-gray">{{fileName}}</p>
</div>
<div class="flex flex-right flex-align-c g-5">
<button class="" (click)="downFile()"><i class="icon img-download-white"></i> 保存图像</button>
<button class="" (click)="hide()"><i class="anticon anticon-close"></i> 退出查看</button>
</div>
</div>
</div>
`,
providers: [DomHandler]
})
export class FilePreview implements OnInit, AfterViewInit, OnDestroy, AfterViewChecked {
@Input() style: any;
@Input() styleClass: string;
fileUrl: string;//文件的url包括主机、端口等信息
fileType: string;//image:图片 video:视频 other:其他
fileName: string;//文件名称
_visible: boolean;
imgDefaultWidth=500;
imgInit=false;
imgDefaultHeight=500;
isDragWhell=false;//是否可以拖拽
previousX:number=0;//前一步鼠标X的位置
previousY:number=0;//前一步鼠标Y的位置
filePreviewClick: boolean;
documentClickListener: any;
set visible(visible: boolean) {
this._visible = visible;
}
get visible() {
return this._visible;
}
@ViewChild('container') containerViewChild: ElementRef;
@ViewChild('previewBox') previewBoxViewChild: ElementRef;
@ViewChild('imgPreview') imgPreviewrViewChild: ElementRef;
container: any;
previewBox: any;
imgPreview: any;
constructor(public el: ElementRef, public domHandler: DomHandler, public renderer: Renderer2,private ngZone: NgZone,public readonly httpService: HttpService,) {
}
ngOnInit() {
}
ngAfterViewInit() {
this.container = <HTMLElement> this.containerViewChild.nativeElement;
this.previewBox = <HTMLElement> this.previewBoxViewChild.nativeElement;
this.imgPreview = <HTMLElement> this.imgPreviewrViewChild.nativeElement;
this.dragWhell();
this.imgZoomBindEvent();
}
ngAfterViewChecked() {
this.initImg();
}
//图片加载后按比例调整大小
initImg(){
if (this.imgPreview&&!this.imgInit) {
let image = new Image();
let defaultWidth = this.imgDefaultWidth;
let defaultHeight = this.imgDefaultHeight;
//原图片原始地址(用于获取原图片的真实宽高,当<img>标签指定了宽、高时不受影响)
let imgSrc= this.imgPreview.getAttribute("src");
if(!imgSrc){
return;
}
image.src = imgSrc;
//当图片比图片框小
if (image.width < defaultWidth && image.height < defaultHeight) {
if(image.width>=image.height){
this.imgPreview.setAttribute("width", defaultWidth + "px");
this.imgPreview.setAttribute("height", defaultWidth * (image.height / image.width) + "px");
}else{
this.imgPreview.setAttribute("width", defaultHeight * (image.width / image.height) + "px");
this.imgPreview.setAttribute("height", defaultHeight + "px");
}
} else {
//原图片宽高比例 大于 图片框宽高比例,则以框的宽为标准缩放,反之以框的高为标准缩放
if (defaultWidth / defaultHeight <= image.width / image.height) {
//原图片宽高比例 大于 图片框宽高比例
this.imgPreview.setAttribute("width", defaultWidth + "px");//以框的宽度为标准
this.imgPreview.setAttribute("height", defaultWidth * (image.height / image.width) + "px");
}
else {
//原图片宽高比例 小于 图片框宽高比例
this.imgPreview.setAttribute("width", defaultHeight * (image.width / image.height) + "px");//以框的高度为标准
this.imgPreview.setAttribute("height", defaultHeight + "px");
}
}
this.imgAlginCenter(parseInt(this.imgPreview.getAttribute("width")),parseInt(this.imgPreview.getAttribute("height")));
this.imgInit=true;
}
}
imgAlginCenter(imgWidth,imgHeight){
this.previewBox.style.left=(document.body.offsetWidth-imgWidth)/2+'px'
this.previewBox.style.top=(document.body.offsetHeight-imgHeight)/2+'px'
}
/**
* 图片缩放事件绑定
*/
imgZoomBindEvent(){
let eventType;
if (window.navigator.userAgent.toLowerCase().indexOf('firefox') != -1) {
eventType='DOMMouseScroll';
} else {
eventType='mousewheel';
}
this.previewBox.addEventListener(eventType, (ev) => {
//判断放大或缩小,(ev.detail > 0) 缩小否则放大
let delta=ev.detail ? ev.detail > 0 : ev.wheelDelta < 0;
this.imgZoom(delta);
});
}
/**
* 图片拖拽和放大
* 计算前一步鼠标的位置和下一步鼠标的位置,来实现
*/
dragWhell() {
this.imgPreview.onmousedown=(ev) => {
this.previewBox.style.cursor = "move";
this.previewBox.onmousemove=(ev)=>{
let disX = 0;
let disY = 0;
if(!this.previousX){
this.previousX=ev.clientX;
}else{
disX=ev.clientX-this.previousX;
this.previousX=ev.clientX;
}
if(!this.previousY){
this.previousY=ev.clientY;
}else{
disY=ev.clientY-this.previousY;
this.previousY=ev.clientY;
}
this.previewBox.style.left = disX + this.previewBox.offsetLeft + 'px';
this.previewBox.style.top = disY + this.previewBox.offsetTop + 'px';
};
return false;
};
this.imgPreview.onmouseup=(ev)=>{
this.previousX=null;
this.previousY=null;
this.previewBox.style.cursor="default";
this.previewBox.onmousemove=null;
return false;
}
}
imgZoom(delta:any){
if(delta){
if(this.imgPreview.offsetWidth<100||this.imgPreview.offsetHeight<100){
return;
}
}else{
if(this.imgPreview.offsetWidth>2048||this.imgPreview.offsetHeight>2048){
return;
}
}
//图片放大缩小因子
let ratioDelta = !delta ? 1 + 0.05 : 1 - 0.05;
//图片外层容器的位置
let previewBoxLeft=this.previewBox.offsetLeft;
let previewBoxTop=this.previewBox.offsetTop;
//图片旧的宽度高度
let imgPreviewOffsetWidth=this.imgPreview.offsetWidth;
let imgPreviewOffsetHeight=this.imgPreview.offsetHeight;
//图片新的宽度高度
let w = imgPreviewOffsetWidth * ratioDelta;
let h = imgPreviewOffsetHeight * ratioDelta;
//图片缩放后宽度,高度改变的量
let widthChangeValue=imgPreviewOffsetWidth-w;
let widthChangeHeight=imgPreviewOffsetHeight-h;
this.previewBox.style.left =(previewBoxLeft+widthChangeValue/2)+ 'px';
this.previewBox.style.top =(previewBoxTop+widthChangeHeight/2) + 'px';
this.imgPreview.style.width=w +'px';
this.imgPreview.style.height=h +'px';
}
setFileInfo(fileUrl, fileName) {
this.fileUrl = fileUrl;
if (this.fileUrl) {
let fileNameByUrl = this.fileUrl.split("/");
let fileTypeByUrl = this.fileUrl.split(".");
if (fileName) {
this.fileName = fileName;
} else {
this.fileName = fileNameByUrl[fileNameByUrl.length - 1];
}
this.toImageType(fileTypeByUrl[fileTypeByUrl.length - 1]);
}
}
toImageType(value) {
if (value && (value.toLowerCase() == 'jpg' || value.toLowerCase() == 'png' || value.toLowerCase() == 'jpeg' || value.toLowerCase() == 'jpg')) {
this.fileType = 'image';
}
}
show(event: MouseEvent, fileUrl?, fileName?) {
this.setFileInfo(fileUrl, fileName)
this.filePreviewClick = true;
//延时计算位置
this.visible = true;
this.domHandler.fadeIn(this.container, 250);
//this.bindDocumentClickListener();
if (event) {
event.preventDefault();
}
}
hide() {
this.domHandler.fadeIn(this.container, 0);
this.visible = false;
//this.unbindDocumentClickListener();
}
toggle(event?: MouseEvent) {
if (this.visible)
this.hide();
else
this.show(event);
}
bindDocumentClickListener() {
if (!this.documentClickListener) {
this.documentClickListener = this.renderer.listen('document', 'click', (event) => {
if (this.visible && ((event.button !== 2 && !this.filePreviewClick))) {
this.hide();
}
this.filePreviewClick = false;
});
}
}
unbindDocumentClickListener() {
if (this.documentClickListener) {
this.documentClickListener();
this.documentClickListener = null;
}
}
filePreviewEventStopPropagation(event) {
event.stopPropagation();
}
ngOnDestroy() {
this.unbindDocumentClickListener();
}
//文件下载
downFile() {
let httpUrlReg=new RegExp("^(http|https|ftp)://([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+))?");
let url=this.fileUrl.replace(httpUrlReg,"")
this.httpService.downFileByUrl(url,this.fileName);
}
}
@NgModule({
imports: [CommonModule],
exports: [FilePreview],
declarations: [FilePreview],
entryComponents: [FilePreview],
})
export class FilePreviewModule {
}
httpService用来处理http请求,用到了NzMessageService这个是NG-ZORRO(一个Angular的控件库)
/**
* @Author: MarkBell
* @Description:
* @Date 2017/11/7
*/
import {Inject, Injectable, EventEmitter} from '@angular/core';
import {Observable} from "rxjs/Observable";
import {HttpClient, HttpResponse, HttpErrorResponse} from '@angular/common/http';
import {
Headers, Http, JsonpModule, XHRBackend, RequestOptions, RequestOptionsArgs, Jsonp,
JSONPBackend, URLSearchParams, QueryEncoder, ResponseContentType
} from '@angular/http';
import {NzMessageService} from "../ui-component/components/ng-zorro-antd.module";
export const API_URL: string = 'API_URL';
export const AISP_URL: string = 'MESSAGE_SERVICE_URL';
export const FLIGHT_PLANELOGO_PATH: string = 'FLIGHT_PLANELOGO_PATH';
export const FLIGHT_LOGO_PATH: string = 'FLIGHT_LOGO_PATH';
export const HTTP_SERVICE: string = 'HTTP_SERVICE';
@Injectable()
export class HttpService {
private _loading;
/** 是否正在加载中 */
readonly loading: boolean;
constructor(private http: HttpClient,private jsonpHttp: Jsonp, public readonly messageService: NzMessageService,) {
}
//基本的http application/json请求
request(method: any, url: any, data?: any): Observable<any> {
return this.http.request(method, url, {body: data, observe: 'response'}).map((res) => {
return res;
})
}
//基本的http Post请求
requestPost(url: any, data?: any): Observable<any> {
return this.http.post(url, data).map((res) => {
return res;
})
}
//基本的http Get请求
requestGet(url: any, data?: any): Observable<any> {
return this.http.get( url,data).map((res) => {
return res;
})
}
//基本的http application/json Blob请求
requestBlob(url: any, data?: any): Observable<any> {
return this.http.request("post", url, {body: data, observe: 'response', responseType: 'blob'});
}
//基本的http Post Blob请求
requestPostBlob(url: any, data?: any): Observable<any> {
return this.http.post(url, data,{ observe: 'response',responseType: 'blob'})
}
//Blob文件转换下载
downFile(result, fileName, fileType?) {
let data = result.body;
if(data.size<=0){
this.messageService.remove();
this.messageService.info("没有数据")
return;
}
var blob = new Blob([data], {type: fileType});
var objectUrl = URL.createObjectURL(blob);
var a = document.createElement('a');
a.setAttribute('style', 'display:none');
a.setAttribute('href', objectUrl);
a.setAttribute('download', fileName);
a.click();
URL.revokeObjectURL(objectUrl);
}
//Blob文件转换下载
downFileByUrl(url, fileName) {
var a = document.createElement('a');
a.setAttribute('style', 'display:none');
a.setAttribute('href', url);
a.setAttribute('download', fileName);
a.click();
}
private catchError(self: HttpService) {
return (res: Response) => {
if (res.status === 401 || res.status === 403) {
console.log(res);
}
if (res.status === 500 || res.status === 501) {
console.log(res);
}
return Observable.throw(res);
};
}
}
DomHandler一个操作dom的工具
/**
* @Author: MarkBell
* @Description:
* @Date 2018/4/27
*/
import { Injectable } from '@angular/core';
@Injectable()
export class DomHandler {
public static zindex: number = 1000;
private calculatedScrollbarWidth: number = null;
public addClass(element: any, className: string): void {
if (element.classList)
element.classList.add(className);
else
element.className += ' ' + className;
}
public addMultipleClasses(element: any, className: string): void {
if (element.classList) {
let styles: string[] = className.split(' ');
for (let i = 0; i < styles.length; i++) {
element.classList.add(styles[i]);
}
}
else {
let styles: string[] = className.split(' ');
for (let i = 0; i < styles.length; i++) {
element.className += ' ' + styles[i];
}
}
}
public removeClass(element: any, className: string): void {
if (element.classList)
element.classList.remove(className);
else
element.className = element.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
}
public hasClass(element: any, className: string): boolean {
if (element.classList)
return element.classList.contains(className);
else
return new RegExp('(^| )' + className + '( |$)', 'gi').test(element.className);
}
public siblings(element: any): any {
return Array.prototype.filter.call(element.parentNode.children, function (child) {
return child !== element;
});
}
public find(element: any, selector: string): any[] {
return element.querySelectorAll(selector);
}
public findSingle(element: any, selector: string): any {
return element.querySelector(selector);
}
public index(element: any): number {
let children = element.parentNode.childNodes;
let num = 0;
for (var i = 0; i < children.length; i++) {
if (children[i] == element) return num;
if (children[i].nodeType == 1) num++;
}
return -1;
}
public relativePosition(element: any, target: any): void {
let elementDimensions = element.offsetParent ? { width: element.offsetWidth, height: element.offsetHeight } : this.getHiddenElementDimensions(element);
let targetHeight = target.offsetHeight;
let targetWidth = target.offsetWidth;
let targetOffset = target.getBoundingClientRect();
let windowScrollTop = this.getWindowScrollTop();
let viewport = this.getViewport();
let top, left;
if ((targetOffset.top + targetHeight + elementDimensions.height) > viewport.height) {
top = -1 * (elementDimensions.height);
if(targetOffset.top + top < 0) {
top = 0;
}
}
else {
top = targetHeight;
}
if ((targetOffset.left + elementDimensions.width) > viewport.width)
left = targetWidth - elementDimensions.width;
else
left = 0;
element.style.top = top + 'px';
element.style.left = left + 'px';
}
public absolutePosition(element: any, target: any): void {
let elementDimensions = element.offsetParent ? { width: element.offsetWidth, height: element.offsetHeight } : this.getHiddenElementDimensions(element);
let elementOuterHeight = elementDimensions.height;
let elementOuterWidth = elementDimensions.width;
let targetOuterHeight = target.offsetHeight;
let targetOuterWidth = target.offsetWidth;
let targetOffset = target.getBoundingClientRect();
let windowScrollTop = this.getWindowScrollTop();
let windowScrollLeft = this.getWindowScrollLeft();
let viewport = this.getViewport();
let top, left;
if (targetOffset.top + targetOuterHeight + elementOuterHeight > viewport.height) {
top = targetOffset.top + windowScrollTop - elementOuterHeight;
if(top < 0) {
top = 0 + windowScrollTop;
}
}
else {
top = targetOuterHeight + targetOffset.top + windowScrollTop;
}
if (targetOffset.left + targetOuterWidth + elementOuterWidth > viewport.width)
left = targetOffset.left + windowScrollLeft + targetOuterWidth - elementOuterWidth;
else
left = targetOffset.left + windowScrollLeft;
element.style.top = top + 'px';
element.style.left = left + 'px';
}
public getHiddenElementOuterHeight(element: any): number {
element.style.visibility = 'hidden';
element.style.display = 'block';
let elementHeight = element.offsetHeight;
element.style.display = 'none';
element.style.visibility = 'visible';
return elementHeight;
}
public getHiddenElementOuterWidth(element: any): number {
element.style.visibility = 'hidden';
element.style.display = 'block';
let elementWidth = element.offsetWidth;
element.style.display = 'none';
element.style.visibility = 'visible';
return elementWidth;
}
public getHiddenElementDimensions(element: any): any {
let dimensions: any = {};
element.style.visibility = 'hidden';
element.style.display = 'block';
dimensions.width = element.offsetWidth;
dimensions.height = element.offsetHeight;
element.style.display = 'none';
element.style.visibility = 'visible';
return dimensions;
}
public scrollInView(container, item) {
let borderTopValue: string = getComputedStyle(container).getPropertyValue('borderTopWidth');
let borderTop: number = borderTopValue ? parseFloat(borderTopValue) : 0;
let paddingTopValue: string = getComputedStyle(container).getPropertyValue('paddingTop');
let paddingTop: number = paddingTopValue ? parseFloat(paddingTopValue) : 0;
let containerRect = container.getBoundingClientRect();
let itemRect = item.getBoundingClientRect();
let offset = (itemRect.top + document.body.scrollTop) - (containerRect.top + document.body.scrollTop) - borderTop - paddingTop;
let scroll = container.scrollTop;
let elementHeight = container.clientHeight;
let itemHeight = this.getOuterHeight(item);
if (offset < 0) {
container.scrollTop = scroll + offset;
}
else if ((offset + itemHeight) > elementHeight) {
container.scrollTop = scroll + offset - elementHeight + itemHeight;
}
}
public fadeIn(element, duration: number): void {
element.style.opacity = 0;
let last = +new Date();
let opacity = 0;
let tick = function () {
opacity = +element.style.opacity + (new Date().getTime() - last) / duration;
element.style.opacity = opacity;
last = +new Date();
if (+opacity < 1) {
(window.requestAnimationFrame && requestAnimationFrame(tick)) || setTimeout(tick, 16);
}
};
tick();
}
public fadeOut(element, ms) {
var opacity = 1,
interval = 50,
duration = ms,
gap = interval / duration;
let fading = setInterval(() => {
opacity = opacity - gap;
if (opacity <= 0) {
opacity = 0;
clearInterval(fading);
}
element.style.opacity = opacity;
}, interval);
}
public getWindowScrollTop(): number {
let doc = document.documentElement;
return (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
}
public getWindowScrollLeft(): number {
let doc = document.documentElement;
return (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
}
public matches(element, selector: string): boolean {
var p = Element.prototype;
var f = p['matches'] || p.webkitMatchesSelector || p['mozMatchesSelector'] || p.msMatchesSelector || function (s) {
return [].indexOf.call(document.querySelectorAll(s), this) !== -1;
};
return f.call(element, selector);
}
public getOuterWidth(el, margin?) {
let width = el.offsetWidth;
if (margin) {
let style = getComputedStyle(el);
width += parseFloat(style.marginLeft) + parseFloat(style.marginRight);
}
return width;
}
public getHorizontalPadding(el) {
let style = getComputedStyle(el);
return parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
}
public getHorizontalMargin(el) {
let style = getComputedStyle(el);
return parseFloat(style.marginLeft) + parseFloat(style.marginRight);
}
public innerWidth(el) {
let width = el.offsetWidth;
let style = getComputedStyle(el);
width += parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
return width;
}
public width(el) {
let width = el.offsetWidth;
let style = getComputedStyle(el);
width -= parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
return width;
}
public getInnerHeight(el) {
let height = el.offsetHeight;
let style = getComputedStyle(el);
height += parseFloat(style.paddingTop) + parseFloat(style.paddingBottom);
return height;
}
public getOuterHeight(el, margin?) {
let height = el.offsetHeight;
if (margin) {
let style = getComputedStyle(el);
height += parseFloat(style.marginTop) + parseFloat(style.marginBottom);
}
return height;
}
public getHeight(el): number {
let height = el.offsetHeight;
let style = getComputedStyle(el);
height -= parseFloat(style.paddingTop) + parseFloat(style.paddingBottom) + parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
return height;
}
public getWidth(el): number {
let width = el.offsetWidth;
let style = getComputedStyle(el);
width -= parseFloat(style.paddingLeft) + parseFloat(style.paddingRight) + parseFloat(style.borderLeftWidth) + parseFloat(style.borderRightWidth);
return width;
}
public getViewport(): any {
let win = window,
d = document,
e = d.documentElement,
g = d.getElementsByTagName('body')[0],
w = win.innerWidth || e.clientWidth || g.clientWidth,
h = win.innerHeight || e.clientHeight || g.clientHeight;
return { width: w, height: h };
}
public getOffset(el) {
let rect = el.getBoundingClientRect();
return {
top: rect.top + document.body.scrollTop,
left: rect.left + document.body.scrollLeft
};
}
getUserAgent(): string {
return navigator.userAgent;
}
isIE() {
var ua = window.navigator.userAgent;
var msie = ua.indexOf('MSIE ');
if (msie > 0) {
// IE 10 or older => return version number
return true;
}
var trident = ua.indexOf('Trident/');
if (trident > 0) {
// IE 11 => return version number
var rv = ua.indexOf('rv:');
return true;
}
var edge = ua.indexOf('Edge/');
if (edge > 0) {
// Edge (IE 12+) => return version number
return true;
}
// other browser
return false;
}
appendChild(element: any, target: any) {
if(this.isElement(target))
target.appendChild(element);
else if(target.el && target.el.nativeElement)
target.el.nativeElement.appendChild(element);
else
throw 'Cannot append ' + target + ' to ' + element;
}
removeChild(element: any, target: any) {
if(this.isElement(target))
target.removeChild(element);
else if(target.el && target.el.nativeElement)
target.el.nativeElement.removeChild(element);
else
throw 'Cannot remove ' + element + ' from ' + target;
}
isElement(obj: any) {
return (typeof HTMLElement === "object" ? obj instanceof HTMLElement :
obj && typeof obj === "object" && obj !== null && obj.nodeType === 1 && typeof obj.nodeName === "string"
);
}
calculateScrollbarWidth(): number {
if(this.calculatedScrollbarWidth !== null)
return this.calculatedScrollbarWidth;
let scrollDiv = document.createElement("div");
scrollDiv.className = "ui-scrollbar-measure";
document.body.appendChild(scrollDiv);
let scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
document.body.removeChild(scrollDiv);
this.calculatedScrollbarWidth = scrollbarWidth;
return scrollbarWidth;
}
public invokeElementMethod(element: any, methodName: string, args?: any[]): void {
(element as any)[methodName].apply(element, args);
}
public clearSelection(): void {
if(window.getSelection) {
if(window.getSelection().empty) {
window.getSelection().empty();
} else if(window.getSelection().removeAllRanges && window.getSelection().rangeCount > 0 && window.getSelection().getRangeAt(0).getClientRects().length > 0) {
window.getSelection().removeAllRanges();
}
}
else if(document['selection'] && document['selection'].empty) {
try {
document['selection'].empty();
} catch(error) {
//ignore IE bug
}
}
}
}