angular scroll by mouse and by space - javascript

There is an application that listens to scrolling the screen by the user.
So that's why if the scrolling is done with the mouse wheel, the data comes an order of magnitude slower than if you scroll the page with a space.
Server code is not laid out, there is no point.
Below is the application code.
#HostListener('window:scroll', ['$event']) checkScroll() {
if (!this.getdata) {
const componentPosition = this.el.nativeElement.offsetTop;
const componentHeight = this.el.nativeElement.getBoundingClientRect().height;
const scrollPosition = window.pageYOffset;
const windowheight = window.outerHeight;
const needposition = componentPosition + componentHeight - windowheight - 500;
if (scrollPosition >= needposition) {
this.getdata = true;
this.getMoreNew();
}
}
}
getMoreNew() {
if (!this.nomoredata) {
const city = this.city;
const find = this.find;
const last = this.persones[this.persones.length - 1]['orderbyname'];
this.httpClient.post<Array<any>>('/assets/api/index.php', {action: 'person', type: this.type, last, limit: this.limit, city, find })
.subscribe(
data => {
if (data.length === 0) {
this.nomoredata = true;
this.getdata = false;
} else {
this.persones = this.persones.concat(data);
this.getdata = false;
}
}
);
} else {
this.getdata = false;
}
}
See screenshot from devtools:

I am not entirely sure what the question is, but I think it's that your scroll is too slow and you want to have a way to help offload the computation for the event? You could try using a debounce to ignore the scrolling until the user is done.

Related

Is it recreate in variable memory for each scroll event?

I have an element with the name ".offer" class right above the footer inside the page. When I approach this element in the scroll movement, I want the sticky header to close.
I wrote a function in scroll event for this but I am curious about something.
When I create a function like the example below, are the variables in the function re-created in memory for each scroll move? Or what's the truth for performance in a simple page scroll event?
const stickyNearOfferClose = () => {
let scrollPos;
let header = document.querySelector("header");
const documentOffset = document.querySelector(".offer").offsetTop;
header.classList.toggle("sticky", window.scrollY);
scrollPos = window.scrollY;
if (scrollPos >= documentOffset) {
header.classList.add("hide");
} else {
header.classList.remove("hide");
}
};
window.addEventListener("scroll", function () {
stickyNearOfferClose();
});
In addition to the above question
When I want to use the above function for one or more scroll actions,
Should we use it define variables inside a function or within an object for both in terms of performance and usability?
I shared two different examples below. Which one do you think should be?
const Obj = {
scrollPos : null,
elHeader: document.querySelector("header"),
documentOffset: document.querySelector(".offer").offsetTop,
// otherfunction (){},
stickyNearOfferClose() {
Obj.elHeader.classList.toggle("sticky", window.scrollY);
if (Obj.scrollPos >= Obj.documentOffset) {
Obj.elHeader.classList.add("hide");
} else {
Obj.elHeader.classList.remove("hide");
}
},
// init(){}
};
window.addEventListener("scroll", function () {
Obj.scrollPos = window.scrollY;
requestAnimationFrame(Obj.stickyNearOfferClose);
});
// OR
const Obj = {
// variables
stickyNearOfferClose() {
let scrollPos;
const elHeader = document.querySelector("header");
const elOffer = document.querySelector(".offer");
const documentOffset = elOffer.offsetTop;
let stickyClose = () => {
elHeader.classList.toggle("sticky", window.scrollY);
if (scrollPos >= documentOffset) {
elHeader.classList.add("hide");
} else {
elHeader.classList.remove("hide");
}
};
window.addEventListener("scroll", () => {
scrollPos = window.scrollY;
requestAnimationFrame(stickyClose);
});
},
spyScrolling() {
let scrollPos;
const sections = document.querySelectorAll(".hero");
let scrollActiveUrl = () => {
for (let s in sections) {
if (
sections.hasOwnProperty(s) &&
sections[s].offsetTop <= scrollPos + 150
) {
const id = sections[s].id;
document.querySelector(".active").classList.remove("active");
document.querySelector(`a[href*=${id}]`).classList.add("active");
}
}
};
window.addEventListener("scroll", () => {
scrollPos = document.documentElement.scrollTop || document.body.scrollTop;
requestAnimationFrame(scrollActiveUrl);
});
}
init(){
this.stickyNearOfferClose();
this.spyScrolling()
}
};
Yes, the variables in the function will be recreated on every scroll events. But in your case, you can put some variables outside of the function to save a bit.
As per MDN, you shouldn't call expensive DOM manipulations directly in the function. Instead, it is recommended to throttle the event using requestAnimationFrame(), setTimeout(), or a CustomEvent. You can learn more at here.
Following is the improved example of your code:
let scrollPos;
const elHeader = document.querySelector("header");
const elOffer = document.querySelector(".offer");
// Note: I'm assuming that `documentOffset` don't need to be updated on every scroll events.
// Update it as `scrollPos` if it need to be updated on every scroll events.
const documentOffset = elOffter.offsetTop;;
const stickyNearOfferClose = () => {
// Note: Please move the following line in the scroll event
// if it's also needed to be called on every scroll events.
elHeader.classList.toggle("sticky", window.scrollY);
if (scrollPos >= documentOffset) {
elHeader.classList.add("hide");
} else {
elHeader.classList.remove("hide");
}
};
window.addEventListener("scroll", function () {
scrollPos = window.scrollY;
requestAnimationFrame(stickyNearOfferClose);
});

Vanilla JS Swipe Function Now Overrides Links/Expected Behaviour

I have written a detect swipe left or right script; it has one unintended consequence. Links that reside within the swipeable container are no longer clickable.
I was wondering if anyone had any solutions they could point me to or any thoughts to experiment with my code.
I have tried:
Adding/toggling with event.preventDefault() within the Pointer Events
Looking at other get gesture type scripts, they include a distance threshold. If it is does not meet the threshold, I remove all event listeners. Adding this creates an unexpected result of working, only if you click the button twice.
Find the clickable elements within the container such as a span a , add an incremental click event listener then change the window location ref. Kinda works, but when you swipe near the span; the selection takes over and swipe actions stop. Experimenting with css touch-action: none or user-select:none have not improved the experience. It feels like I am breaking the default behaviour and reinventing the wheel.
link to a demo here on JS Bin
JS code here
window.addEventListener('load', () => {
let test = new getSwipeX({
elementId: 'container'
});
// previous attempt - adding a click on top of the browser default
let grabBtn = document.getElementsByClassName('button')[0];
grabBtn.addEventListener('click', (e) => {
window.location.href = 'www.google.co.uk'
});
})
function getSwipeX({elementId}) {
this.e = document.getElementsByClassName(elementId)[0];
this.initialPosition = 0;
this.lastPosition = 0;
this.threshold = 200;
this.diffInPosition = null;
this.diffVsThreshold = null;
this.gestureState = 0;
this.getTouchStart = (event) => {
event.stopPropagation();
if (window.PointerEvent) {
this.e.setPointerCapture(event.pointerId);
}
return this.initalTouchPos = this.getGesturePoint(event);
}
this.getTouchMove = (event) => {
event.stopPropagation();
return this.lastPosition = this.getGesturePoint(event);
}
this.getTouchEnd = (event) => {
event.stopPropagation();
if (window.PointerEvent) {
this.e.releasePointerCapture(event.pointerId);
}
this.doSomething();
this.initialPosition = 0;
}
this.getGesturePoint = (event) => {
this.point = event.pageX
return this.point;
}
this.whatGestureDirection = (event) => {
this.diffInPosition = this.initalTouchPos - this.lastPosition;
this.diffVsThreshold = Math.abs(this.diffInPosition) > this.threshold;
(Math.sign(this.diffInPosition) > 0) ? this.gestureState = 'L' : (Math.sign(this.diffInPosition) < 0) ? this.gestureState = 'R' : this.gestureState = 'N';
return [this.diffInPosition, this.diffVsThreshold, this.gestureState];
}
this.doSomething = (event) => {
let [gestureDelta,gestureThreshold,gestureDirection] = this.whatGestureDirection();
// USE THIS TO DEBUG
console.log(gestureDelta,gestureThreshold,gestureDirection);
if (gestureThreshold) {
(gestureDirection == 'L') ? console.log('Left') : console.log('Right');
} else {
this.e.removeEventListener('pointerdown', this.getTouchStart, true);
this.e.removeEventListener('pointermove', this.getTouchMove, true);
this.e.removeEventListener('pointerup', this.getTouchEnd, true);
this.e.removeEventListener('pointercancel', this.getTouchEnd, true);
}
}
if (window.PointerEvent) {
this.e.addEventListener('pointerdown', this.getTouchStart, true);
this.e.addEventListener('pointermove', this.getTouchMove, true);
this.e.addEventListener('pointerup', this.getTouchEnd, true);
this.e.addEventListener('pointercancel', this.getTouchEnd, true);
}
}

request firing twice for the scroll

I am developing a website using VueJs and I have used JQuery for the scroll function.
I am incrementing the page no when the user scrolls to the bottom of the page.
At first (page = 1) it shows just one request.
But when scroll down then two requests are firing at once (page = 2, page = 3).
getDisplay() function used to get the data and I have set LIMIT and OFFSET values for that.
mounted: function() {
this.getDisplay();
this.bindScroll();
},
methods: {
bindScroll: function(){
var vm = this;
$(window).bind('scroll', function () {
if ($(window).scrollTop() === $(document).height() - $(window).height()) {
if(vm.isMorePost > 0){
vm.showLoading = 1;
vm.page++;
vm.getDisplay();
}
}
})
},
getDisplay: function() {
var input = {
name: this.userId,
recordPerPage: this.recordPerPage,
page: this.page
};
this.loadingIcon = 1;
this.$http.get('/display-view/show/get-user-display', {params: input}).then(function(response) {
this.display = this.display.concat(response.data.data);
this.isMorePost = (response.data.data.length);
if(response.data.data.length == 0){
this.showLoading = 0
}else {
this.showLoading = 1
}
}.bind(this));
}
},
I need to fire just one request with incremented page no when the user meets bottom of the page. How can I solve this?

Error: Cannot use the same canvas during multiple render() operations. PDF.js issue

I'm working on web application(c#) with angularjs (client side) project.
we've requirement to show pdf on webpage with next/previous button options.
for that We'd used pdf.js (By Mozilla Foundation), and we've implemented code as per given example.
we have created one angular directive to things works correctly.
Everything works fine while it is loading page first time, now come to point:
After loading new source URL it is throwing below error:
pdf.js?v=1641729671:12170 Uncaught (in promise) Error: Cannot use the same canvas during multiple render() operations. Use different canvas or ensure previous operations were cancelled or completed.
at InternalRenderTask.initializeGraphics (pdf.js?v=1641729671:12170)
at pdf.js?v=1641729671:10666
I've tried below things from my side:
Tried to render pdf by re-linking directive.
Tried to render all pdf by page cleanup function but still it causes an issue.
Tried to render pdf pages by canceling pages in between but it's not working.
Tried to clear canvas and context and reinitialized it but no trick.
Tried to create dynamic canvases by different different IDS but it didn't work.
Tried to assign class instead of Id of pdf viewer but it didn't work.
I did research on google but I didn't get any working solution :(
now let me post my client side code for better understanding.
below is my directive code:
angular.module('angularPDFView', [])
.directive('pdfviewer', ['$parse', function ($parse) {
var canvas = null;
var instance_id = null;
var context = null;
return {
restrict: "E",
template: '<canvas></canvas>',
scope: {
onPageLoad: '&',
loadProgress: '&',
src: '#',
id: '='
},
controller: ['$scope', function ($scope) {
$scope.pageNum = 1;
$scope.pdfDoc = null;
$scope.scale = 1.0;
$scope.rotation = 0;
$scope.pageNumPending = null;
$scope.renderTask;
$scope.path = null;
$scope.getRandomSpan = function () {
return Math.floor(Math.random() * 1000000000);
};
$scope.loadPDF = function (path) {
$scope.path = path;
var randNum = $scope.getRandomSpan();
pdfjsLib.GlobalWorkerOptions.workerSrc = '/Content/js/pdf.worker.js?v=' + randNum;
console.log('loadPDF ', path);
$scope.scale = 1.0;
$scope.rotation = 0;
var loadingTask = pdfjsLib.getDocument(path);
loadingTask.promise.then(function (_pdfDoc) {
$scope.pdfDoc = _pdfDoc;
$scope.renderPage($scope.pageNum);
});
};
$scope.renderPage = function (num) {
pageRendering = true;
// Using promise to fetch the page
$scope.pdfDoc.getPage(num).then(function (page) {
if ($scope.renderTask) {
try {
$scope.renderTask.cancel();
} catch (e) {
//
}
}
var viewport = page.getViewport({ scale: $scope.scale });
canvas.height = viewport.height;
canvas.width = viewport.width;
// Render PDF page into canvas context
var renderContext = {
canvasContext: context,
viewport: viewport,
continueCallback: function (cont) {
cont();
}
};
$scope.renderTask = page.render(renderContext);
// Wait for rendering to finish
try {
$scope.renderTask.promise.then(function () {
pageRendering = false;
if ($scope.pageNumPending !== null) {
// New page rendering is pending
$scope.renderPage($scope.pageNumPending);
$scope.pageNumPending = null;
}
$scope.$apply(function () {
$scope.onPageLoad({
page: $scope.pageNum,
total: $scope.pdfDoc.numPages
});
});
});
} catch (e) {
//
}
});
// Update page counters
$scope.pageNum = num;
};
$scope.queueRenderPage = function (num) {
if (context) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
}
if ($scope.pageRendering) {
$scope.pageNumPending = num;
} else {
$scope.renderPage(num);
}
};
$scope.$on('pdfviewer.prevPage', function () {
if ($scope.pageNum <= 1) {
return;
}
$scope.pageNum--;
$scope.queueRenderPage($scope.pageNum);
});
/**
* Displays next page.
*/
$scope.$on('pdfviewer.nextPage', function () {
if ($scope.pageNum >= $scope.pdfDoc.numPages) {
return;
}
$scope.pageNum++;
$scope.queueRenderPage($scope.pageNum);
});
$scope.$on('pdfviewer.gotoPage', function (evt, id, page) {
if (id !== instance_id) {
return;
}
if ($scope.pdfDoc === null) {
$scope.pageNum = page;
$scope.loadPDF($scope.path);
} else {
if (page >= 1 && page <= $scope.pdfDoc.numPages) {
$scope.pageNum = page;
$scope.renderPage($scope.pageNum);
}
}
});
}],
link: function (scope, iElement, iAttr) {
canvas = iElement.find('canvas')[0];
context = canvas.getContext('2d');
instance_id = iAttr.id;
iAttr.$observe('src', function (v) {
console.log('src attribute changed, new value is', v);
if (v !== undefined && v !== null && v !== '') {
scope.pageNum = 1;
scope.loadPDF(scope.src);
}
});
}
};
}])
.service("angularPDFViewerService", ['$rootScope', function ($rootScope) {
var svc = {};
svc.nextPage = function () {
$rootScope.$broadcast('pdfviewer.nextPage');
};
svc.prevPage = function () {
$rootScope.$broadcast('pdfviewer.prevPage');
};
svc.Instance = function (id) {
var instance_id = id;
return {
prevPage: function () {
$rootScope.$broadcast('pdfviewer.prevPage');
},
nextPage: function () {
$rootScope.$broadcast('pdfviewer.nextPage');
},
gotoPage: function (page) {
$rootScope.$broadcast('pdfviewer.gotoPage', instance_id, page);
}
};
};
return svc;
}]);
..
How can I cancel render operation or Is there any way to use different canvas to load PDF ?
Any help would be appreciated.
Thanks in advance.!
Use a "render in progress" flag:
var renderInProgress;
var renderTask;
if (renderInProgress) {
//ensure previous operations were cancelled or completed.
.then(function() {
renderTask = doRender();
});
} else {
renderTask = doRender();
};
function doRender() {
renderInProgress = true;
var renderOp = page.render(renderContext);
renderOp.promise = renderOp.promise.then(function () {
//Do whatever
}).finally(function() {
renderInProgress = false;
});
return renderOp;
}
After two days of efforts finally I've fixed pdf load issue, console error is still there but functionality is working correctly now :)
I'm posting answer that might help someone, here are little steps I did:
Visit: https://cdnjs.com/libraries/pdf.js
I've downloaded most recent version of PDF.js and Pdf-worker.js from above mentioned link.
And just replaced my old reference libraries with newer one and that's it.
It works like charm in my angular application now!!!

Check if scrollIntoView is completed

I am using the following function in a single page application in Angular. When I click on the menu item, I scroll to the relevant div.
scroll (el) {
this.sharedService.isClicked.next(true);
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
How do I check if the element has finished scrolling? I want to avoid the setTimeout function.
hope this helps..
var scrollIntoviewCompleted = false;
var currentVisible = false;
var onceVisible = false;
$(window).scroll(function(){
$.fn.isOnScreen = function(){
var element = this.get(0);
var bounds = element.getBoundingClientRect();
return bounds.top < window.innerHeight && bounds.bottom > 0;
}
if ($('.targetdiv').isOnScreen()) {
currentVisible = true;
onceVisible =true;
}
else
{
currentVisible = false;
}
if(onceVisible == true && currentVisible == false){
scrollIntoviewCompleted = true;
console.log("scrollIntoViewCompleted");
}
});
Based on an answer in this question I was able to make this for my tests... and this assumes that you are #Injecting window into your angular components
let win: Window;
const onScrollEnd = (fn: () => void): void => {
let scrollTimeout: number | undefined;
const listener = (): void => {
win.clearTimeout(scrollTimeout);
scrollTimeout = win.setTimeout(() => {
win.removeEventListener('scroll', listener);
fn();
}, 100);
};
win.addEventListener('scroll', listener);
};
beforeEach(() => {
//Standard component test setup stuff goes here...
win = TestBed.get('Window');
});
And then in my test I can do this:
it('should scroll the window down when...', (done: DoneFn) => {
component.shouldScroll = true;
fixture.detectChanges();
onScrollEnd(() => {
expect(win.scrollY).toBeGreaterThan(0);
done();
});
});
This worked for me (I declare this snippet public domain, feel free to re-use):
scrollAndDo = function(currPageXOffset,currPageYOffset) {
$('#SomeElement')[0].scrollIntoView({behavior: 'smooth',block:'nearest',inline: 'nearest'});
currPageXOffset = window.pageXOffset;
currPageYOffset = window.pageYOffset;
var scrollDone = setInterval(function () {
if ( currPageXOffset == window.pageXOffset && currPageYOffset == window.pageYOffset ) {
clearInterval(scrollDone);
console.log('I have finished scrolling');
}
currPageXOffset = window.pageXOffset;
currPageYOffset = window.pageYOffset;
},50);
};
scrollAndDo();

Categories