JS:
$.fn.blink = function(times, duration) {
times = times || 2;
while (times--) {
this.fadeTo(duration, 0).fadeTo(duration, 1);
}
};
This makes blinking text. At last blink I want to execute a function so I changed it like this:
$.fn.blink = function(times, duration, callback) {
times = times || 2;
while (times--) {
this.fadeTo(duration, 0).fadeTo(duration, 1);
}
if (typeof callback == 'function') { // make sure the callback is a function
callback.call(this); // brings the scope to the callback
}
};
The execution of callback is not at the right place. It seems to be fired when blinking starts. Any ideas how to call the function at last blinking?
FIDDLE
I wrote you a fiddle. The problem with your code was, that the loop iterated through your blinking stuff to fast, and the complete function was called immediately.
$(document).ready(function () {
$('.blink').blink(5, 500, shout);
});
function shout() {
alert('finished blinking');
}
$.fn.blinkOnce = function (duration, callback) {
this.fadeTo(duration, 0).fadeTo(duration, 1, function () {
if (typeof callback == 'function') callback.call(this);
});
};
$.fn.blink = function (times, duration, callback) {
var toBlink = this;
var blinkComplete = function () {
times--;
if (times == 0) callback.call(this);
else toBlink.blinkOnce(duration, blinkComplete);
}
toBlink.blinkOnce(duration, blinkComplete);
return true;
};
http://jsfiddle.net/xgeLu/
jQuery animations have a complete callback argument since these methods are asynchronous
Try:
while (times--) {
this.fadeTo(duration, 0).fadeTo(duration, 1, function(){
/*last animation is complete*/
if (typeof callback == 'function') { // make sure the callback is a function
callback.call(this); // brings the scope to the callback
}
});
}
API Reference: http://api.jquery.com/fadeTo/
Related
Trying to optimise the UX of some JS, including using Event Delegation and Throttling - but cannot seem to combine the approaches.
I'm looking for a way to listen for an event (scroll, mousemove etc.), identify the element, then call a relevant function (throttled).
I've tried various things, including trying to add a wrapper/intermediate function (handler) to pass multiple functions - but the end result is the same ... I end up with nothing back from the Throttle function.
The only thing I've managed to get to work is to produce a multi-throttle function, where I manually repeat the throttle functionality per function I wish to throttle.
function mythrottle() {
var timer1, timer2;
return function() {
var now = Date.now();
var last1 = timer1;
var last2 = timer2;
if(!last1) { timer1 = now; callback1(); return; }
if(!last2) { timer2 = now; callback2(); return; }
if(last1 + 500 > now) { return; }
timer1 = now; callback1();
if(last2 + 1500 > now) { return; }
timer2 = now; callback2();
}
}
function callback1(){
console.log("callback 1 firing");
}
function callback2(){
console.log("callback 2 firing");
}
window.addEventListener('scroll', mythrottle());
Ideally, I'd like;
1) a single eventlistener per event
2) the ability to filter/qualify the event (click on button or link or span etc.)
3) to then call the throttle function with a specific function (dependent on the filter/qualifier), passing the function and the delay
4) the throttle to handle the event with the delay (immediate fire, then wait)
That will avoid what I'm currently having to do, which is copy and ready several little chunks for each individual thing (and I'll have to wrap each with qualifiers/filters!).
There are many ways of doing what you're trying to do. In any case, to make it work dynamically (adding/removing/configuring callbacks), you need a state. That's why I wrapped everything in a class.
Each callback will be tracked independently (to allow having different delays as per request). After instantiation, you can add more callbacks and/or events. Obviously, this example can be extended/improved, but I think is a good start and it should meet all your requirements.
If you have any question about the code below, please feel free to ask and I'll be glad to answer.
NOTE: I've used modern syntax, but it can be easily rewritten to increase compatibility.
// -- THROTTLER --
class Throttler {
constructor(args) {
this.queue = [];
this.throttle = typeof args.throttle === "number" ? args.throttle : 500; // GLOBAL THROTTLE
this.threshold = typeof args.threshold === "number" ? args.threshold : 50; // GLOBAL THRESHOLD
// BIND METHODS
this.addCallback = this.addCallback.bind(this);
this.addEvent = this.addEvent.bind(this);
this.handler = this.handler.bind(this);
if (Array.isArray(args.queue)) {
// SETUP INITIAL CALLBACKS
args.queue.forEach(this.addCallback);
}
if (Array.isArray(args.events)) {
// SETUP INITIAL EVENTS
args.events.forEach(this.addEvent);
}
}
addCallback(cb) {
if (typeof cb === "function") {
this.queue.push({
throttle: this.throttle,
threshold: this.threshold,
callback: cb,
timer: null,
ref: Date.now(),
elapsed: 0
});
} else if (typeof cb === "object") {
this.queue.push({
throttle: cb.debounce ? undefined : this.throttle,
threshold: this.threshold,
...cb,
timer: null,
ref: Date.now(),
elapsed: 0
});
}
}
addEvent(eventName) {
// ATTACH HANDLER
window.addEventListener(eventName, this.handler);
}
handler(e) {
this.queue.forEach((elem) => {
const NOW = Date.now();
if (typeof elem.throttle === "number") { // THROTTLE
elem.elapsed += NOW - elem.ref;
elem.ref = NOW;
if (elem.elapsed >= elem.throttle) {
// EXECUTE CALLBACK
if (typeof elem.callback === "function") {
if (typeof elem.selector !== "string" || (typeof e.target.matches === "function" && e.target.matches(elem.selector))) {
if (typeof elem.eventName !== "string" || e.type === elem.eventName) {
elem.callback(e);
}
}
}
// RESET COUNTER
elem.elapsed = 0;
}
// KILL TIMER
elem.timer && clearInterval(elem.timer);
// RE-CREATE TIMER
elem.timer = setTimeout(() => {
// RESET COUNTER
elem.elapsed = 0;
// RESET TIMER
elem.timer = null;
}, elem.threshold);
} else if (typeof elem.debounce === "number") { // DEBOUNCE
// KILL TIMER
elem.timer && clearInterval(elem.timer);
// RE-CREATE TIMER
elem.timer = setTimeout(() => {
// EXECUTE CALLBACK
if (typeof elem.callback === "function") {
if (typeof elem.selector !== "string" || (typeof e.target.matches === "function" && e.target.matches(elem.selector))) {
if (typeof elem.eventName !== "string" || e.type === elem.eventName) {
elem.callback(e);
}
}
}
// RESET TIMER
elem.timer = null;
}, elem.debounce);
}
});
}
}
// --- USAGE ---
// INSTANTIATE THROTTLER
const myThrottler = new Throttler({
throttle: 1500,
events: ['scroll', 'mousemove'],
queue: [callback1, callback2]
});
// ADD ANOTHER EVENT
myThrottler.addEvent('resize');
// ADD CONDITIONAL CALLBACK
myThrottler.addCallback({
callback: callback3,
selector: '*',
eventName: 'mousemove'
});
// ADD CUSTOM DELAY DEBOUNCED CALLBACK
myThrottler.addCallback({
callback: callback4,
debounce: 2000
});
// ADD CUSTOM DELAY THROTTLED CALLBACK
myThrottler.addCallback({
callback: callback5,
throttle: 3000
});
// --- CALLBACKS ---
function callback1() {
console.log("CB 1");
}
function callback2() {
console.log("CB 2");
}
function callback3() {
console.log("CB 3");
}
function callback4() {
console.log("CB 4");
}
function callback5() {
console.log("CB 5");
}
Here's a simple throttle wrapper that can handle multiple callbacks and timers - however at the cost of:
window namespace pollution (window._throttleWait)
possible clash in callback throttling (throttling the exact same method twice will use the same thottle-wait timing)
But it's mighty small:
function throttle(callback, limit = 150) {
window._throttleWait = window._throttleWait ?? {}
return function() {
if (!window._throttleWait[callback]) {
callback.call();
window._throttleWait[callback] = true;
setTimeout(function() {
delete window._throttleWait[callback];
}, limit);
}
}
}
I would like to create a copy of a game called "Hill Climb Racing" using pixi.js and matter.js. I have found a simple scene manager and scene object written in TypeScript and I converted it to JavaScript but some part of code is not working... I would like to use the function onUpdate like this: gameplay.onUpdate(function() {} );, but I do not know how to call the callback in onUpdate function by function this.update().
var callback = function() {}
this.onUpdate = function(callback) {
if (callback && typeof callback == "function") {
callback();
console.log("looP");
}
}
this.update = function() {
callback();
}
If I understand you correctly, I prefer to show you how to pass a callback as "local" function so it will be more clear. (And this is usually use case)
this.onUpdate = function(callback) {
if(callback && typeof callback == "function"){
callback();
console.log("looP");
}
}
this.update = function() {
this.onUpdate(() => {
console.log('onUpdate callback');
});
}
For example:
function myObj() {
this.onUpdate = function(callback) {
if (callback && typeof callback == "function") {
callback();
console.log("looP");
}
}
this.update = function() {
this.onUpdate(() => {
console.log('onUpdate callback');
});
}
}
const a = new myObj();
//a.update();
a.onUpdate(() => {
console.log('this is my callback!');
});
I have the following JS code:
var delay = 5000;
function init() {
setInterval(getFileCount, delay);
}
function getFileCount() {
$.get('/notification/course-file-count', function(response) {
if (response.items.length === 0) {
return false;
}
// Do stuff with response
});
}
On page load I'm calling the init() function. The idea is to start the interval and call the getFileCount() function every 5 seconds.
So, the interval waits 5s after the page loads and runs, but it always makes the Ajax call twice.
What am I missing?
UPDATE:
I know the init() function is triggered twice on page load (thanks to the comment by Yury Tarabanko). I don't quite understand, why. The almost-full code:
$(function() {
'use strict';
function handleCourseNotification() {
var delay = 5000;
function init() {
setInterval(getFileCount, delay);
}
function getFileCount() {
$.get('/notification/course-file-count', function(response) {
if (response.items.length === 0) {
return false;
}
updateCourseList(response.items);
});
}
function updateCourseList(items) {
// update course list...
}
return {
init: init
};
}
if ($('#js-auth-course-list').length) {
var notificationHandler = handleCourseNotification();
notificationHandler.init();
}
});
It's a small module, which I initialize after page load, if a specific element is available in the DOM - $('#js-auth-course-list'). Why is init called 2 times actually? I directly call it once.
In general, it is not a good idea to call asynchronous calls inside a setInterval() because you do not know the exact response time. So, you could end up calling the second async function before the response from the first call has returned.
You can try with setTimeout() like this:
var delay = 5000;
var async = function() {
$.get('/notification/course-file-count', function(response) {
if (response.items.length === 0) {
return false;
}
// Do stuff with response
// Call the async function again
setTimeout(function() {
async();
}, delay);
});
}
async();
i have this Prototype function
(function(window, undefined){
function Waypoint (el, callback, offset){
this.el = el;
this.cb = callback;
this.offset = offset || 0;
window.optimizedScroll.add(this.passedWaypoint);
//window.optimizedScroll.add(this.passedWaypoint.call(this)); doesn't work
}
Waypoint.prototype.passedWaypoint = function(){
//if element passes a point execute the callback function
//this.cb(); //undefined
console.log(this); //refers to window and not to my obj
};
window.Waypoint = Waypoint;
})(this);
var myElement1 = new Waypoint("myElement", function(){
console.log("i have traveled so far");
});
and this optimized scrolling from this page
https://developer.mozilla.org/en-US/docs/Web/Events/resize (i only changed resize with scroll)
var optimizedScroll = (function(window, undefined) {
var callbacks = [], running = false;
// fired on resize event
function scroll() {
if (!running) {
running = true;
window.requestAnimationFrame(runCallbacks);
}
}
// run the actual callbacks
function runCallbacks() {
callbacks.forEach(function(callback) {
callback();
});
running = false;
}
// adds callback to loop
function addCallback(callback) {
if (callback) {
callbacks.push(callback);
}
}
return {
// public method to add additional callback
add: function(callback) {
if (!callbacks.length) {
window.addEventListener('scroll', scroll);
}
addCallback(callback);
}
};
})(this);
the callback function gets executed when i scroll, but i have a problem with the small word "this". how can i achive that "this" refers to my obj and not to window. i played around with "call" but i didn't get it...
gregor
the comment of #MysterX is correct ;)
window.optimizedScroll.add(this.passedWaypoint.bind(this));
not "call" what i tried
THANK YOU!! ;)
I'm trying to test in which function this callback function in executed. It should return a boolean value.
I hope you know what I mean.
Here the code example:
function test(par, callback) {
// ...
if (typeof callback == 'function') { // make sure the callback is a function
callback.call(this);
}
}
test("par", function() {
console.log("Test if in function test: " + "<if this is in function test>");
});
Is this similar to instanceof?
There's a non-standard way of doing it since the removal of arguments.caller
function test(par, callback) {
// ...
if (typeof callback == 'function') { // make sure the callback is a function
callback.call(this);
}
}
test("par", function cb() {
var isTestCaller = cb.caller === test;
console.log("Test if in function test: " + isTestCaller);
});
Another possible way doing it through error stacks (still non-standard):
var checkCaller = function(fnName) {
var e = new Error();
var caller = e.stack.split('\n')[2].trim().split(' ')[1];
return caller === fnName;
}
function wrapper(){
console.log(checkCaller('wrapper'));
}
wrapper();