Made a lot of changes to the webtoon reader which may be better for the end user based on some light testing with one user. Not ready for prime-time though

This commit is contained in:
Joseph Milazzo 2023-12-09 08:46:11 -06:00
parent d48e59384f
commit f2befdd7be
3 changed files with 55 additions and 15 deletions

View file

@ -31,7 +31,7 @@
<img src="{{item.src}}" style="display: block" <img src="{{item.src}}" style="display: block"
class="mx-auto {{pageNum === item.page && showDebugOutline() ? 'active': ''}} {{areImagesWiderThanWindow ? 'full-width' : ''}} {{initFinished ? '' : 'full-opacity'}}" class="mx-auto {{pageNum === item.page && showDebugOutline() ? 'active': ''}} {{areImagesWiderThanWindow ? 'full-width' : ''}} {{initFinished ? '' : 'full-opacity'}}"
*ngIf="item.page >= pageNum - bufferPages && item.page <= pageNum + bufferPages" rel="nofollow" alt="image" *ngIf="item.page >= pageNum - bufferPages && item.page <= pageNum + bufferPages" rel="nofollow" alt="image"
(load)="onImageLoad($event)" id="page-{{item.page}}" [attr.page]="item.page" ondragstart="return false;" onselectstart="return false;"> (load)="onImageLoad($event)" (touchstart)="onTouchStart($event)" id="page-{{item.page}}" [attr.page]="item.page" ondragstart="return false;" onselectstart="return false;">
</ng-container> </ng-container>
<div *ngIf="atBottom" class="spacer bottom" role="alert" (click)="loadNextChapter.emit()"> <div *ngIf="atBottom" class="spacer bottom" role="alert" (click)="loadNextChapter.emit()">

View file

@ -21,11 +21,10 @@
.text { .text {
z-index: 101; z-index: 101;
} }
} }
img, .full-width { img, .full-width {
max-width: 100% !important; max-width: 100% !important;
height: auto; height: auto;

View file

@ -157,11 +157,15 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
/** /**
* Debug mode. Will show extra information. Use bitwise (|) operators between different modes to enable different output * Debug mode. Will show extra information. Use bitwise (|) operators between different modes to enable different output
*/ */
debugMode: DEBUG_MODES = DEBUG_MODES.None; debugMode: DEBUG_MODES = DEBUG_MODES.Outline | DEBUG_MODES.Logs;
/** /**
* Debug mode. Will filter out any messages in here so they don't hit the log * Debug mode. Will filter out any messages in here, so they don't hit the log
*/ */
debugLogFilter: Array<string> = ['[PREFETCH]', '[Intersection]', '[Visibility]', '[Image Load]']; debugLogFilter: Array<string> = ['[PREFETCH]', '[Intersection]', '[Visibility]', '[Image Load]'];
/**
* Total Height of all the images
*/
totalHeight: number = 0;
get minPageLoaded() { get minPageLoaded() {
return Math.min(...Object.values(this.imagesLoaded)); return Math.min(...Object.values(this.imagesLoaded));
@ -212,6 +216,10 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
} }
ngOnInit(): void { ngOnInit(): void {
this.totalHeight = this.mangaReaderService.maxHeight();
this.cdRef.markForCheck();
this.initScrollHandler(); this.initScrollHandler();
this.recalculateImageWidth(); this.recalculateImageWidth();
@ -220,7 +228,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
this.goToPage.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(page => { this.goToPage.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(page => {
const isSamePage = this.pageNum === page; const isSamePage = this.pageNum === page;
if (isSamePage) { return; } if (isSamePage) { return; }
this.debugLog('[GoToPage] jump has occured from ' + this.pageNum + ' to ' + page); this.debugLog('[GoToPage] jump has occurred from ' + this.pageNum + ' to ' + page);
if (this.pageNum < page) { if (this.pageNum < page) {
this.scrollingDirection = PAGING_DIRECTION.FORWARD; this.scrollingDirection = PAGING_DIRECTION.FORWARD;
@ -317,15 +325,26 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
} }
handleScrollEndEvent(event?: any) { handleScrollEndEvent(event?: any) {
if (!this.isScrolling) { if (this.isScrolling) { return; }
const closestImages = Array.from(document.querySelectorAll('img[id^="page-"]')) as HTMLImageElement[]; return;
const img = this.findClosestVisibleImage(closestImages); const closestImages = Array.from(document.querySelectorAll('img[id^="page-"]')) as HTMLImageElement[];
const img = this.findClosestVisibleImage(closestImages);
if (img != null) { const newPageNum = parseInt(img?.getAttribute('page') || this.pageNum + '', 10);
this.setPageNum(parseInt(img.getAttribute('page') || this.pageNum + '', 10));
} // When a scroll end occurs on the last pre-fetched page, the next load event can cause the page number to be set to the end
// of the pages, thus jumping the user a ton of pages.
console.log('scroll end page delta: ', Math.abs(newPageNum - this.pageNum));
if (Math.abs(newPageNum - this.pageNum) <= 10 && newPageNum != this.pageNum) {
console.log('Closest page is: ', newPageNum)
this.setPageNum(newPageNum);
} }
// if (img != null) {
// console.log('Closest page is: ', img.getAttribute('page'))
// this.setPageNum(parseInt(img.getAttribute('page') || this.pageNum + '', 10));
// }
} }
getTotalHeight() { getTotalHeight() {
@ -354,6 +373,8 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
if (this.scrollingDirection === PAGING_DIRECTION.FORWARD) { if (this.scrollingDirection === PAGING_DIRECTION.FORWARD) {
const totalHeight = this.getTotalHeight(); const totalHeight = this.getTotalHeight();
const totalScroll = this.getTotalScroll(); const totalScroll = this.getTotalScroll();
const pageDeltaToTriggerBottomLoad = 2;
// If we were at top but have started scrolling down past page 0, remove top spacer // If we were at top but have started scrolling down past page 0, remove top spacer
if (this.atTop && this.pageNum > 0) { if (this.atTop && this.pageNum > 0) {
@ -361,7 +382,15 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
this.cdRef.markForCheck(); this.cdRef.markForCheck();
} }
if (totalScroll === totalHeight && !this.atBottom) { // If we scroll faster than the images can load, we can end up in a situation where we hit the bottom before we should,
// We can either check that the page num is within 10 of the total pages
// or we could insert a div that equal the space of the image heights combined to ensure the scroll bar
// Same situation, but when the next load of pages, we hit this condition. hence I'm adding a check on the page number to ensure
if (totalScroll === totalHeight && !this.atBottom && Math.abs(this.pageNum - this.totalPages) <= pageDeltaToTriggerBottomLoad) {
this.debugLog('total Scroll: ', totalScroll);
this.debugLog('total height: ', totalHeight);
this.debugLog('page delta: ', Math.abs(this.pageNum - this.totalPages));
this.debugLog('We hit the bottom of the viewport!');
this.atBottom = true; this.atBottom = true;
this.cdRef.markForCheck(); this.cdRef.markForCheck();
this.setPageNum(this.totalPages); this.setPageNum(this.totalPages);
@ -471,7 +500,8 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
const rect = image.getBoundingClientRect(); const rect = image.getBoundingClientRect();
// Calculate the distance of the current image to the top of the viewport. // Calculate the distance of the current image to the top of the viewport.
const distanceToTop = Math.abs(rect.top); const distanceToTop = Math.abs(rect.top) - rect.height / 2;
this.debugLog(`Image ${image.getAttribute('page')} is ${distanceToTop}px from top`)
// Check if the image is visible within the viewport. // Check if the image is visible within the viewport.
if (distanceToTop < closestDistanceToTop) { if (distanceToTop < closestDistanceToTop) {
@ -502,9 +532,19 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
this.cdRef.markForCheck(); this.cdRef.markForCheck();
} }
/**
* When the user touches an image, let's set it as the active page. This ensures their touch always sets the active page
* @param event
*/
onTouchStart(event: TouchEvent) {
const newPage = parseInt((event.target as HTMLImageElement).getAttribute('page') || this.pageNum + '', 10);
this.debugLog('touch start: ', newPage);
this.setPageNum(newPage);
}
/** /**
* Callback for an image onLoad. At this point the image is already rendered in DOM (may not be visible) * Callback for an image onLoad. At this point the image is already rendered in DOM (may not be visible)
* This will be used to scroll to current page for intial load * This will be used to scroll to current page for initial load
* @param event * @param event
*/ */
onImageLoad(event: any) { onImageLoad(event: any) {
@ -530,6 +570,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
this.currentPageElem = this.document.querySelector('img#page-' + this.pageNum); this.currentPageElem = this.document.querySelector('img#page-' + this.pageNum);
// There needs to be a bit of time before we scroll // There needs to be a bit of time before we scroll
if (this.currentPageElem && !this.isElementVisible(this.currentPageElem)) { if (this.currentPageElem && !this.isElementVisible(this.currentPageElem)) {
this.debugLog('image load, scrolling to page', this.pageNum);
this.scrollToCurrentPage(); this.scrollToCurrentPage();
} else { } else {
this.initFinished = true; this.initFinished = true;