improving lightbox

bug fixes with new image loading and fullscreen mode
This commit is contained in:
Braun Patrik 2017-03-25 21:59:30 +01:00
parent 52544ae956
commit f1d8364a2e
9 changed files with 218 additions and 84 deletions

View File

@ -30,6 +30,7 @@ import {Config} from "./config/Config";
import {GalleryMapComponent} from "./gallery/map/map.gallery.component"; import {GalleryMapComponent} from "./gallery/map/map.gallery.component";
import {GalleryMapLightboxComponent} from "./gallery/map/lightbox/lightbox.map.gallery.component"; import {GalleryMapLightboxComponent} from "./gallery/map/lightbox/lightbox.map.gallery.component";
import {ThumbnailManagerService} from "./gallery/thumnailManager.service"; import {ThumbnailManagerService} from "./gallery/thumnailManager.service";
import {OverlayService} from "./gallery/overlay.service";
@NgModule({ @NgModule({
imports: [ imports: [
@ -68,7 +69,8 @@ import {ThumbnailManagerService} from "./gallery/thumnailManager.service";
AuthenticationService, AuthenticationService,
ThumbnailLoaderService, ThumbnailLoaderService,
ThumbnailManagerService, ThumbnailManagerService,
FullScreenService], FullScreenService,
OverlayService],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })

View File

@ -1,9 +1,12 @@
import {Injectable} from "@angular/core"; import {Injectable} from "@angular/core";
import {Event} from "../../../common/event/Event";
@Injectable() @Injectable()
export class FullScreenService { export class FullScreenService {
OnFullScreenChange = new Event<boolean>();
public isFullScreenEnabled(): boolean { public isFullScreenEnabled(): boolean {
return !!(document.fullscreenElement || document['mozFullScreenElement'] || document.webkitFullscreenElement); return !!(document.fullscreenElement || document['mozFullScreenElement'] || document.webkitFullscreenElement);
} }
@ -22,6 +25,7 @@ export class FullScreenService {
} else if (element.msRequestFullscreen) { } else if (element.msRequestFullscreen) {
element.msRequestFullscreen(); element.msRequestFullscreen();
} }
this.OnFullScreenChange.trigger(true);
} }
public exitFullScreen() { public exitFullScreen() {
@ -36,6 +40,7 @@ export class FullScreenService {
} else if (document.webkitExitFullscreen) { } else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen(); document.webkitExitFullscreen();
} }
this.OnFullScreenChange.trigger(false);
} }
} }

View File

@ -1,13 +1,14 @@
import { import {
Component,
Input,
ElementRef,
OnChanges,
ViewChild,
ViewChildren,
QueryList,
AfterViewInit, AfterViewInit,
HostListener ChangeDetectorRef,
Component,
ElementRef,
HostListener,
Input,
OnChanges,
QueryList,
ViewChild,
ViewChildren
} from "@angular/core"; } from "@angular/core";
import {PhotoDTO} from "../../../../common/entities/PhotoDTO"; import {PhotoDTO} from "../../../../common/entities/PhotoDTO";
import {GridRowBuilder} from "./GridRowBuilder"; import {GridRowBuilder} from "./GridRowBuilder";
@ -15,31 +16,33 @@ import {GalleryLightboxComponent} from "../lightbox/lightbox.gallery.component";
import {GridPhoto} from "./GridPhoto"; import {GridPhoto} from "./GridPhoto";
import {GalleryPhotoComponent} from "./photo/photo.grid.gallery.component"; import {GalleryPhotoComponent} from "./photo/photo.grid.gallery.component";
import {Config} from "../../config/Config"; import {Config} from "../../config/Config";
import {OverlayService} from "../overlay.service";
@Component({ @Component({
selector: 'gallery-grid', selector: 'gallery-grid',
templateUrl: 'app/gallery/grid/grid.gallery.component.html', templateUrl: 'app/gallery/grid/grid.gallery.component.html',
styleUrls: ['app/gallery/grid/grid.gallery.component.css'], styleUrls: ['app/gallery/grid/grid.gallery.component.css'],
}) })
export class GalleryGridComponent implements OnChanges,AfterViewInit { export class GalleryGridComponent implements OnChanges, AfterViewInit {
@ViewChild('gridContainer') gridContainer:ElementRef; @ViewChild('gridContainer') gridContainer: ElementRef;
@ViewChildren(GalleryPhotoComponent) gridPhotoQL:QueryList<GalleryPhotoComponent>; @ViewChildren(GalleryPhotoComponent) gridPhotoQL: QueryList<GalleryPhotoComponent>;
@Input() photos: Array<PhotoDTO>; @Input() photos: Array<PhotoDTO>;
@Input() lightbox:GalleryLightboxComponent; @Input() lightbox: GalleryLightboxComponent;
photosToRender:Array<GridPhoto> = []; photosToRender: Array<GridPhoto> = [];
containerWidth:number = 0; containerWidth: number = 0;
private IMAGE_MARGIN = 2; private IMAGE_MARGIN = 2;
private TARGET_COL_COUNT = 5; private TARGET_COL_COUNT = 5;
private MIN_ROW_COUNT = 2; private MIN_ROW_COUNT = 2;
private MAX_ROW_COUNT = 5; private MAX_ROW_COUNT = 5;
onScrollFired = false; private onScrollFired = false;
private scrollbarWidth = 0;
constructor() { constructor(private overlayService: OverlayService, private changeDetector: ChangeDetectorRef) {
} }
ngOnChanges() { ngOnChanges() {
@ -61,16 +64,17 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
} }
this.updateContainerWidth(); this.updateContainerWidth();
this.sortPhotos(); this.sortPhotos();
//render the same amount of images on resize
let renderedIndex = this.renderedPhotoIndex;
this.clearRenderedPhotos(); this.clearRenderedPhotos();
setImmediate(() => { this.renderPhotos(renderedIndex);
this.renderPhotos();
});
} }
isAfterViewInit:boolean = false;
isAfterViewInit: boolean = false;
ngAfterViewInit() { ngAfterViewInit() {
this.lightbox.gridPhotoQL = this.gridPhotoQL; this.lightbox.setGridPhotoQL(this.gridPhotoQL);
//TODO: implement scroll detection //TODO: implement scroll detection
@ -96,6 +100,7 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
private clearRenderedPhotos() { private clearRenderedPhotos() {
this.photosToRender = []; this.photosToRender = [];
this.renderedPhotoIndex = 0; this.renderedPhotoIndex = 0;
this.changeDetector.detectChanges();
} }
private mergeNewPhotos() { private mergeNewPhotos() {
@ -123,9 +128,9 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
} }
private renderedPhotoIndex:number = 0; private renderedPhotoIndex: number = 0;
private renderPhotos() { private renderPhotos(numberOfPhotos: number = 0) {
if (this.containerWidth == 0 || this.renderedPhotoIndex >= this.photos.length || !this.shouldRenderMore()) { if (this.containerWidth == 0 || this.renderedPhotoIndex >= this.photos.length || !this.shouldRenderMore()) {
return; return;
} }
@ -133,7 +138,7 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
let renderedContentHeight = 0; let renderedContentHeight = 0;
while (this.renderedPhotoIndex < this.photos.length && this.shouldRenderMore(renderedContentHeight) === true) { while (this.renderedPhotoIndex < this.photos.length && (this.shouldRenderMore(renderedContentHeight) === true || this.renderedPhotoIndex < numberOfPhotos)) {
let ret = this.renderARow(); let ret = this.renderARow();
if (ret === null) { if (ret === null) {
throw new Error("Gridphotos rendering failed"); throw new Error("Gridphotos rendering failed");
@ -149,7 +154,7 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
* @param offset Add height to the client height (conent is not yet added to the dom, but calculate with it) * @param offset Add height to the client height (conent is not yet added to the dom, but calculate with it)
* @returns {boolean} * @returns {boolean}
*/ */
private shouldRenderMore(offset:number = 0):boolean { private shouldRenderMore(offset: number = 0): boolean {
return Config.Client.enableOnScrollRendering === false || return Config.Client.enableOnScrollRendering === false ||
window.scrollY >= (document.body.clientHeight + offset - window.innerHeight) * 0.7 window.scrollY >= (document.body.clientHeight + offset - window.innerHeight) * 0.7
|| (document.body.clientHeight + offset) * 0.85 < window.innerHeight; || (document.body.clientHeight + offset) * 0.85 < window.innerHeight;
@ -164,7 +169,7 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
this.renderPhotos(); this.renderPhotos();
if (Config.Client.enableOnScrollThumbnailPrioritising === true) { if (Config.Client.enableOnScrollThumbnailPrioritising === true) {
this.gridPhotoQL.toArray().forEach((pc:GalleryPhotoComponent) => { this.gridPhotoQL.toArray().forEach((pc: GalleryPhotoComponent) => {
pc.onScroll(); pc.onScroll();
}); });
} }
@ -174,15 +179,16 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
} }
} }
public renderARow():number { public renderARow(): number {
if (this.renderedPhotoIndex >= this.photos.length) { if (this.renderedPhotoIndex >= this.photos.length) {
return null; return null;
} }
let maxRowHeight = window.innerHeight / this.MIN_ROW_COUNT; let maxRowHeight = window.innerHeight / this.MIN_ROW_COUNT;
let minRowHeight = window.innerHeight / this.MAX_ROW_COUNT; let minRowHeight = window.innerHeight / this.MAX_ROW_COUNT;
let photoRowBuilder = new GridRowBuilder(this.photos, this.renderedPhotoIndex, this.IMAGE_MARGIN, this.containerWidth); let photoRowBuilder = new GridRowBuilder(this.photos, this.renderedPhotoIndex, this.IMAGE_MARGIN, this.containerWidth - this.overlayService.getPhantomScrollbarWidth());
photoRowBuilder.addPhotos(this.TARGET_COL_COUNT); photoRowBuilder.addPhotos(this.TARGET_COL_COUNT);
photoRowBuilder.adjustRowHeightBetween(minRowHeight, maxRowHeight); photoRowBuilder.adjustRowHeightBetween(minRowHeight, maxRowHeight);
@ -198,7 +204,7 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
return rowHeight; return rowHeight;
} }
private updateContainerWidth():number { private updateContainerWidth(): number {
if (!this.gridContainer) { if (!this.gridContainer) {
return; return;
} }

View File

@ -37,9 +37,11 @@
<span class="glyphicon glyphicon-remove highlight" (click)="hide()" title="close"></span> <span class="glyphicon glyphicon-remove highlight" (click)="hide()" title="close"></span>
</div> </div>
<div class="navigation-arrow highlight" *ngIf="navigation.hasPrev" id="leftArrow" (click)="prevImage()"><span <div class="navigation-arrow highlight" *ngIf="navigation.hasPrev" title="key: left arrow" id="leftArrow"
(click)="prevImage()"><span
class="glyphicon glyphicon-chevron-left"></span></div> class="glyphicon glyphicon-chevron-left"></span></div>
<div class="navigation-arrow highlight" *ngIf="navigation.hasNext" id="rightArrow" (click)="nextImage()"><span <div class="navigation-arrow highlight" *ngIf="navigation.hasNext" title="key: right arrow" id="rightArrow"
(click)="nextImage()"><span
class="glyphicon glyphicon-chevron-right"></span></div> class="glyphicon glyphicon-chevron-right"></span></div>
</div> </div>
</div> </div>

View File

@ -1,8 +1,19 @@
import {Component, QueryList, Output, EventEmitter, HostListener, ElementRef, ViewChild} from "@angular/core"; import {
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
HostListener,
Output,
QueryList,
ViewChild
} from "@angular/core";
import {PhotoDTO} from "../../../../common/entities/PhotoDTO"; import {PhotoDTO} from "../../../../common/entities/PhotoDTO";
import {GalleryPhotoComponent} from "../grid/photo/photo.grid.gallery.component"; import {GalleryPhotoComponent} from "../grid/photo/photo.grid.gallery.component";
import {Dimension} from "../../model/IRenderable"; import {Dimension} from "../../model/IRenderable";
import {FullScreenService} from "../fullscreen.service"; import {FullScreenService} from "../fullscreen.service";
import {OverlayService} from "../overlay.service";
import {Subscription} from "rxjs";
@Component({ @Component({
selector: 'gallery-lightbox', selector: 'gallery-lightbox',
@ -19,62 +30,80 @@ export class GalleryLightboxComponent {
public blackCanvasOpacity: any = 0; public blackCanvasOpacity: any = 0;
private activePhoto: GalleryPhotoComponent; private activePhoto: GalleryPhotoComponent;
public gridPhotoQL: QueryList<GalleryPhotoComponent>; private gridPhotoQL: QueryList<GalleryPhotoComponent>;
private visible = false; private visible = false;
private changeSubscription: Subscription = null;
@ViewChild("root") elementRef: ElementRef; @ViewChild("root") elementRef: ElementRef;
constructor(private fullScreenService: FullScreenService) { constructor(private fullScreenService: FullScreenService, private changeDetector: ChangeDetectorRef, private overlayService: OverlayService) {
}
//noinspection JSUnusedGlobalSymbols
@HostListener('window:resize', ['$event'])
onResize() {
if (this.activePhoto) {
this.disableAnimation();
this.lightboxDimension.width = this.getScreenWidth();
this.lightboxDimension.height = this.getScreenHeight();
this.updateActivePhoto(this.activePhotoId);
}
} }
public nextImage() { public nextImage() {
this.disableAnimation(); this.disableAnimation();
let pcList = this.gridPhotoQL.toArray(); if (this.activePhotoId + 1 < this.gridPhotoQL.length) {
for (let i = 0; i < pcList.length; i++) { this.showPhoto(this.activePhotoId + 1);
if (pcList[i] === this.activePhoto) { if (this.activePhotoId + 3 >= this.gridPhotoQL.length) {
if (i + 1 < pcList.length) {
this.showPhoto(pcList[i + 1]);
if (i + 3 === pcList.length) {
this.onLastElement.emit({}); //trigger to render more photos if there are this.onLastElement.emit({}); //trigger to render more photos if there are
} }
}
return; return;
} }
} console.warn("can't find photo to show next");
} }
public prevImage() { public prevImage() {
this.disableAnimation(); this.disableAnimation();
let pcList = this.gridPhotoQL.toArray(); if (this.activePhotoId > 0) {
for (let i = 0; i < pcList.length; i++) { this.showPhoto(this.activePhotoId - 1);
if (pcList[i] === this.activePhoto) {
if (i > 0) {
this.showPhoto(pcList[i - 1]);
}
return; return;
} }
} console.warn("can't find photo to show prev");
} }
private showPhoto(photoComponent: GalleryPhotoComponent) { activePhotoId: number = null;
private showPhoto(photoIndex: number) {
this.activePhoto = null;
this.changeDetector.detectChanges();
this.updateActivePhoto(photoIndex);
}
private updateActivePhoto(photoIndex: number) {
let pcList = this.gridPhotoQL.toArray(); let pcList = this.gridPhotoQL.toArray();
let index = pcList.indexOf(photoComponent);
if (index == -1) { if (photoIndex < 0 || photoIndex > this.gridPhotoQL.length) {
throw new Error("Can't find the photo"); throw new Error("Can't find the photo");
} }
this.activePhotoId = photoIndex;
this.activePhoto = pcList[photoIndex];
this.photoDimension = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.photo);
this.navigation.hasPrev = photoIndex > 0;
this.navigation.hasNext = photoIndex + 1 < pcList.length;
let to = this.activePhoto.getDimension();
//if target image out of screen -> scroll to there
if (this.getBodyScrollTop() > to.top || this.getBodyScrollTop() + this.getScreenHeight() < to.top) {
this.setBodyScrollTop(to.top);
}
this.photoDimension = this.calcLightBoxPhotoDimension(photoComponent.gridPhoto.photo);
this.navigation.hasPrev = index > 0;
this.navigation.hasNext = index + 1 < pcList.length;
this.activePhoto = photoComponent;
} }
public show(photo: PhotoDTO) { public show(photo: PhotoDTO) {
@ -90,8 +119,8 @@ export class GalleryLightboxComponent {
this.blackCanvasOpacity = 0; this.blackCanvasOpacity = 0;
this.photoDimension = selectedPhoto.getDimension(); this.photoDimension = selectedPhoto.getDimension();
document.getElementsByTagName('body')[0].style.overflow = 'hidden'; //disable scroll
this.overlayService.showOverlay();
setImmediate(() => { setImmediate(() => {
this.lightboxDimension = <Dimension>{ this.lightboxDimension = <Dimension>{
top: 0, top: 0,
@ -100,19 +129,13 @@ export class GalleryLightboxComponent {
height: this.getScreenHeight() height: this.getScreenHeight()
}; };
this.blackCanvasOpacity = 1.0; this.blackCanvasOpacity = 1.0;
this.showPhoto(selectedPhoto); this.showPhoto(this.gridPhotoQL.toArray().indexOf(selectedPhoto));
}); });
} }
public hide() { public hide() {
this.enableAnimation(); this.enableAnimation();
this.fullScreenService.exitFullScreen(); this.fullScreenService.exitFullScreen();
let to = this.activePhoto.getDimension();
//iff target image out of screen -> scroll to there
if (this.getBodyScrollTop() > to.top || this.getBodyScrollTop() + this.getScreenHeight() < to.top) {
this.setBodyScrollTop(to.top);
}
this.lightboxDimension = this.activePhoto.getDimension(); this.lightboxDimension = this.activePhoto.getDimension();
this.lightboxDimension.top -= this.getBodyScrollTop(); this.lightboxDimension.top -= this.getBodyScrollTop();
@ -121,13 +144,24 @@ export class GalleryLightboxComponent {
setTimeout(() => { setTimeout(() => {
this.visible = false; this.visible = false;
this.activePhoto = null; this.activePhoto = null;
document.getElementsByTagName('body')[0].style.overflow = 'scroll'; this.overlayService.hideOverlay();
}, 500); }, 500);
} }
setGridPhotoQL(value: QueryList<GalleryPhotoComponent>) {
if (this.changeSubscription != null) {
this.changeSubscription.unsubscribe();
}
this.gridPhotoQL = value;
this.changeSubscription = this.gridPhotoQL.changes.subscribe(() => {
if (this.activePhotoId != null && this.gridPhotoQL.length > this.activePhotoId) {
this.updateActivePhoto(this.activePhotoId);
}
});
}
private findPhotoComponent(photo: any) { private findPhotoComponent(photo: any) {
let galleryPhotoComponents = this.gridPhotoQL.toArray(); let galleryPhotoComponents = this.gridPhotoQL.toArray();
for (let i = 0; i < galleryPhotoComponents.length; i++) { for (let i = 0; i < galleryPhotoComponents.length; i++) {
@ -138,15 +172,26 @@ export class GalleryLightboxComponent {
return null; return null;
} }
//noinspection JSUnusedGlobalSymbols
@HostListener('window:keydown', ['$event']) @HostListener('window:keydown', ['$event'])
onKeyPress(e: KeyboardEvent) { onKeyPress(e: KeyboardEvent) {
if (this.visible != true) {
return;
}
let event: KeyboardEvent = window.event ? <any>window.event : e; let event: KeyboardEvent = window.event ? <any>window.event : e;
switch (event.keyCode) { switch (event.keyCode) {
case 37: case 37:
if (this.activePhotoId > 0) {
this.prevImage(); this.prevImage();
}
break; break;
case 39: case 39:
if (this.activePhotoId < this.gridPhotoQL.length - 1) {
this.nextImage(); this.nextImage();
}
break;
case 27: //escape
this.hide();
break; break;
} }
} }

View File

@ -4,12 +4,13 @@
[style.height.%]="imageSize.height" [style.height.%]="imageSize.height"
[src]="thumbnailPath()"/> [src]="thumbnailPath()"/>
<img *ngIf="gridPhoto" <img *ngIf="gridPhoto !== null"
[style.width.%]="imageSize.width" [style.width.%]="imageSize.width"
[style.height.%]="imageSize.height" [style.height.%]="imageSize.height"
[src]="gridPhoto.getPhotoPath()" [src]="gridPhoto.getPhotoPath()"
(load)="onImageLoad()" (load)="onImageLoad()"
(error)="onImageError()"/> (error)="onImageError()"/>
</div> </div>

View File

@ -1,4 +1,4 @@
import {Component, OnChanges, Input, ViewChild, ElementRef} from "@angular/core"; import {Component, Input, OnChanges} from "@angular/core";
import {GridPhoto} from "../../grid/GridPhoto"; import {GridPhoto} from "../../grid/GridPhoto";
@Component({ @Component({
@ -8,12 +8,11 @@ import {GridPhoto} from "../../grid/GridPhoto";
}) })
export class GalleryLightboxPhotoComponent implements OnChanges { export class GalleryLightboxPhotoComponent implements OnChanges {
@Input() gridPhoto:GridPhoto; @Input() gridPhoto: GridPhoto;
public imageSize = {width: "auto", height: "100"}; public imageSize = {width: "auto", height: "100"};
@ViewChild('imgContainer') nativeElement:ElementRef;
imageLoaded:boolean = false; imageLoaded: boolean = false;
constructor() { constructor() {
} }
@ -48,12 +47,12 @@ export class GalleryLightboxPhotoComponent implements OnChanges {
console.error("cant load image"); console.error("cant load image");
} }
public showThumbnail():boolean { public showThumbnail(): boolean {
return this.gridPhoto && !this.imageLoaded && return this.gridPhoto && !this.imageLoaded &&
(this.gridPhoto.isThumbnailAvailable() || this.gridPhoto.isReplacementThumbnailAvailable()); (this.gridPhoto.isThumbnailAvailable() || this.gridPhoto.isReplacementThumbnailAvailable());
} }
public thumbnailPath():string { public thumbnailPath(): string {
if (this.gridPhoto.isThumbnailAvailable() === true) if (this.gridPhoto.isThumbnailAvailable() === true)
return this.gridPhoto.getThumbnailPath(); return this.gridPhoto.getThumbnailPath();

View File

@ -1,9 +1,9 @@
import {Component, Input, OnChanges, ElementRef, ViewChild} from "@angular/core"; import {Component, ElementRef, HostListener, Input, OnChanges, ViewChild} from "@angular/core";
import {PhotoDTO} from "../../../../../common/entities/PhotoDTO"; import {PhotoDTO} from "../../../../../common/entities/PhotoDTO";
import {Dimension} from "../../../model/IRenderable"; import {Dimension} from "../../../model/IRenderable";
import {FullScreenService} from "../../fullscreen.service"; import {FullScreenService} from "../../fullscreen.service";
import {SebmGoogleMap} from "angular2-google-maps/core"; import {SebmGoogleMap} from "angular2-google-maps/core";
import {ThumbnailManagerService, IconThumbnail} from "../../thumnailManager.service"; import {IconThumbnail, ThumbnailManagerService} from "../../thumnailManager.service";
import {IconPhoto} from "../../IconPhoto"; import {IconPhoto} from "../../IconPhoto";
@Component({ @Component({
@ -138,6 +138,20 @@ export class GalleryMapLightboxComponent implements OnChanges {
return window.innerHeight; return window.innerHeight;
} }
//noinspection JSUnusedGlobalSymbols
@HostListener('window:keydown', ['$event'])
onKeyPress(e: KeyboardEvent) {
if (this.visible != true) {
return;
}
let event: KeyboardEvent = window.event ? <any>window.event : e;
switch (event.keyCode) {
case 27: //escape
this.hide();
break;
}
}
} }

View File

@ -0,0 +1,60 @@
import {Injectable} from "@angular/core";
import {Event} from "../../../common/event/Event";
@Injectable()
export class OverlayService {
OnOverlayChange = new Event<boolean>();
private scrollWidth: number = null;
public showOverlay() {
//disable scrolling
document.getElementsByTagName('body')[0].style.overflow = 'hidden';
this.OnOverlayChange.trigger(true);
}
public hideOverlay() {
document.getElementsByTagName('body')[0].style.overflowY = 'scroll';
this.OnOverlayChange.trigger(false);
}
getScrollbarWidth() {
if (this.scrollWidth == null) {
let outer = document.createElement("div");
outer.style.visibility = "hidden";
outer.style.width = "100px";
outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps
document.body.appendChild(outer);
let widthNoScroll = outer.offsetWidth;
// force scrollbars
outer.style.overflow = "scroll";
// add innerdiv
let inner = document.createElement("div");
inner.style.width = "100%";
outer.appendChild(inner);
let widthWithScroll = inner.offsetWidth;
// remove divs
outer.parentNode.removeChild(outer);
this.scrollWidth = widthNoScroll - widthWithScroll;
}
return this.scrollWidth;
}
getPhantomScrollbarWidth() {
if (document.getElementsByTagName('body')[0].style.overflow == 'hidden') {
return this.getScrollbarWidth();
}
return 0;
}
}