I'm new to Nativescript (used to be a Corona/Lua developer) and I need to create a function (similar to a RuntimeEventListener in Lua) that constantly gets user location and updates a dashboard with speed and altitude, for example.
My current code gets this info only when a button is pressed (which does not make sense for the kind of app I am trying to build). Question is, how to create and invoke such listener/function?
I am coding in Javascript and below it is my current code:
var Observable = require("data/observable").Observable;
var frames = require("ui/frame");
var orientation = require('nativescript-orientation');
orientation.enableRotation(); // The screen will rotate
console.log(orientation.getOrientation()); // Returns the enum DeviceOrientation value
var dialogs = require("ui/dialogs");
// Get geo coordinates
var geolocation = require("nativescript-geolocation");
if (!geolocation.isEnabled()) {
geolocation.enableLocationRequest();
}
/*
var watchID
watchId = geolocation.watchLocation(
function (loc) {
if (loc) {
console.log("(watchid) Received location: " + loc);
}
},
function(e){
console.log("(watchid) Error: " + e.message);
},
{desiredAccuracy: 3, updateDistance: 10, minimumUpdateTime : 1000 * 20}); // should update every 20 sec according to google documentation this is not so sure.
*/
//variables for the dashboard and the Origin
var originLoc //holds the lat,long of the starting point
var originHeading = "NNW"
var originTime = "0"
var originDistance = "0"
var mySpeed = "0"
var myDuration = "00:00"
var myDistance = "0"
var myAltitude = "0";
var myDirection;
var butAction = "START" //button action when it starts
var fbMeasurement = "imperial";
//Sets the right heading of the compass (if landscape, subtracts 90 degrees)
function headingCompass(args) {
var compassHead = "";
if (args>12 && args<=34) {
compassHead = "NNE";
} else if (args>34 && args<=57) {
compassHead = "NE";
} else if (args>57 && args<=80) {
compassHead = "ENE";
} else if (args>80 && args<=102) {
compassHead = "E";
} else if (args>102 && args<=124) {
compassHead = "ESE";
} else if (args>124 && args<=147) {
compassHead = "SE";
} else if (args>147 && args<=170) {
compassHead = "SSE";
} else if (args>170 && args<=192) {
compassHead = "S";
} else if (args>192 && args<=215) {
compassHead = "SSW";
} else if (args>215 && args<=237) {
compassHead = "SW";
} else if (args>237 && args<=260) {
compassHead = "WSW";
} else if (args>260 && args<=282) {
compassHead = "W";
} else if (args>282 && args<=305) {
compassHead = "WNW";
} else if (args>305 && args<=327) {
compassHead = "NW";
} else if (args>327 && args<=350) {
compassHead = "NNW";
} else {
compassHead = "N";
}
return compassHead;
}
//Gets current location when app starts
var geolocation = require("nativescript-geolocation");
if (!geolocation.isEnabled()) {
geolocation.enableLocationRequest();
}
var location = geolocation.getCurrentLocation({desiredAccuracy: 3, updateDistance: 10, maximumAge: 20000, timeout: 20000}).
then(function(loc) {
if (loc) {
console.log("Current location is: " + loc);
originLoc = loc;
if (fbMeasurement === "imperial") {
myAltitude = parseInt(loc.altitude * 3.28084);
mySpeed = (loc.speed * 2.23694).toFixed(1);
} else {
mySpeed = loc.speed.toFixed(1);
myAltitude = parseInt(loc.altitude);
}
myDirection = headingCompass(loc.direction)
}
}, function(e){
console.log("Error: " + e.message);
});
function createViewModel() {
var viewModel = new Observable();
viewModel.originHeading = originHeading;
viewModel.originTime = originTime;
viewModel.originDistance = originDistance;
viewModel.mySpeed = mySpeed;
viewModel.myDuration = myDuration;
viewModel.myDistance = myDistance;
viewModel.myAltitude = myAltitude;
viewModel.butAction = butAction;
//STARTs
var watchid;
viewModel.onTapStart = function(args) {
if (butAction==="START") {
//change button color to RED
var btn = args.object;
btn.backgroundColor = "#FF0000";
//change button text to "STOP"
this.set("butAction","STOP");
butAction = "STOP";
watchId = geolocation.watchLocation(
function (loc) {
if (loc) {
console.log("Received location: " + loc);
if (fbMeasurement === "imperial") {
myAltitude = parseInt(loc.altitude * 3.28084);
mySpeed = (loc.speed * 2.23694).toFixed(1);
} else {
mySpeed = loc.speed.toFixed(1);
myAltitude = parseInt(loc.altitude);
}
myDirection = headingCompass(loc.direction);
}
},
function(e){
console.log("Error: " + e.message);
},
{desiredAccuracy: 3, updateDistance: 10, minimumUpdateTime : 1000 * 1}); // should update every 20 sec according to google documentation this is not so sure.
} else {
//change button color to GREEN
var btn = args.object;
btn.backgroundColor = "#00FF00";
//change button text to "START"
this.set("butAction","START")
butAction = "START";
if (watchId) {
geolocation.clearWatch(watchId);
}
}
this.set("myAltitude",myAltitude);
this.set("mySpeed",mySpeed);
this.set("myDistance",myDirection);
}
return viewModel;
}
exports.createViewModel = createViewModel;
The watchlocation method is, in fact, a listener and will update your location when it is changed (based on this arguments). However, you will need to use some observable properties to update the info and reuse it where and when needed. Also, keep in mind that in Android the location is sometimes triggered after some distance (in my case approx. 100 steps gave the difference in the fourth sign after the dot).
If you are familiar with MVVM pattern this is the one used on regular basis in NativeScript applications.Here you can find the article for Data Binding in NativeScript.
So basically just execute your watch function (e.g. using loaded event for your Page) and then watch for changes in the Observable model (e.g. create Observable property latitude and use the updated info when and where needed)
e.g.
vm = new Observable();
vm.set("altitude", someDefaultValue);
vm.set("longitude", someDefaultValue);
geolocation.watchLocation(function(loc) {
vm.set("altitude", loc.altitude);
vm.set("longitude", loc.longitude);
console.log(vm.get("altitude")); // Observable model updated
console.log(vm.get("longitude"));
})
Question: How can I make my files.map(...) pause every 50 iterations?
Problem: the gm().size() is a very expensive function. which completely shits the bed after about 300 iterations. I have a theory that this will be remedied if I let the function keep up.
//interaction happens that will traverse a bunch of folder and create an array of files paths
glob(filePath + '/**/*{.png,.jpg,.gif}', function (er, files) {
var chunksize = 50; // sets the iteration size
if (er) return er;
service.stuff[name] = files.map(function (entry, i) {
return {
identity: getIdentity() //returns the identity(size) of images
};
function getIdentity() {
if(i % chunksize == 0) { // if the 50th iteration
(function(chunksize, i){
setTimeout(function () {
var entrySize = gm(entry) //graphics magic will return size of images based on path.
.size(function (err, size) {
return size;
});
}, 2000); //pause for 2 seconds.
}());
} else {
var entrySize = gm(entry)
.size(function (err, size) {
return size;
});
}
return entrySize.data; //returns identity data.
}
});
});
Alternatively, implement your own batch processor. This alternative to map will only process options.batchSize items at a time, then takes an options.timeoutMs break to give the application time to do other things.
function batchMap(array, fn, options, callback) {
var batchSize = options.batchSize || 100,
timeoutMs = options.timeoutMs || 0;
function map(done, todo) {
if(todo.length > 0) {
setTimeout(function() {
var mapped = todo.slice(0, batchSize).map(fn);
map(done.concat(mapped), todo.slice(batchSize));
}, timeoutMs);
} else {
callback(null, done);
}
}
map([], array);
}
You can use async.mapSeries for this. It waits for each iteration to finish before continuing to the next.
npm install async
var async = require("async");
var noFile = 0;
var done = function (err, result) {
console.log(result);
}
var yourLogic = function(file){
}
var processFile = function(file, callback) {
if(noFile > 50) {
setTimeout(function() {
noFile++;
callback(null, yourLogic(file));
}, 1000);
} else {
noFile++;
callback(null, yourLogic(file));
}
}
async.mapSeries(files, processFile, done);
Sorry, I just feel like shamelessly plugging my new library so I will. It goes something like this:
var CL = require('coastline');
CL.run(function* () {
var files = yield glob(filePath + '/**/*{.png,.jpg,.gif}', CL.cb());
var chunksize = 50;
service.stuff[name] = yield CL.map(files, function* (entry, i) {
if (i && i % chunksize == 0) yield CL.sleep(2000);
var entrySize = yield gm(entry).size(CL.cb());
return {
identity: entrySize.data
};
});
});
Edit: checked and it works, except there is no .data in entrySize, just .width and .height?
Edit: removed var i since we can use array index.
I have an object and I call it many times in my page, but with different parameters.
var lazyLoad = (function () {
var CONFIG = {
block: '',
url: ''
}
function work(){
window.d = document
var buffer = ''
d.write = d.writeln = function(s){ buffer += s }
d.open = d.close = function(){}
s = d.createElement('script')
s.setAttribute('type','text/javascript')
s.setAttribute('src',CONFIG.url)
d.getElementById(CONFIG.block).appendChild(s)
s.onload = function () {
window.setTimeout(function() {
console.warn(CONFIG.block + ' ' + buffer)
d.getElementById(CONFIG.block).innerHTML += buffer
buffer = ''
}, 0)
}
}
return {
init: function (options) {
$.extend(CONFIG, options);
random = $('#'+CONFIG.block).attr('rel')
id = $('#'+CONFIG.block).attr('id').replace(random,'')
id = id.replace('DIV','')
size = id.split('X')
ele_width = size[0] || CONFIG.width
ele_height = size[1] || CONFIG.height
$('#'+CONFIG.block).css({
'width':ele_width+'px',
'height':ele_height+'px',
'background':'url(/static/i/ajax-loading-black.gif) no-repeat center center'
})
console.log(CONFIG.block)
$(window).load(function(){
work()
})
}
}
})();
I call it like this:
lazyLoad.init({
url: 'http://example.com/test1.js',
block: DIVID1
})
Than
lazyLoad.init({
url: 'http://test.com/test2.js',
block: DIVID2
})
And than:
lazyLoad.init({
url: 'http://testdomain.com/test3.js',
block: DIVID3
})
After loading the document I see that each div has width and height, which is applied with this script, but buffer was inserted only in last div.
The problem is that CONFIG is declared in the outer function, since javascript is all single threaded(ignore WebWorkers here =)) at the timeyour work function is called the values in CONFIG are the right ones. But since every time you do $.extend(CONFIG, options); you change the same object by the time s.onload is fired the value left in CONFIG.block is the last one used. Try:
var lazyLoad = (function () {
//var CONFIG = {
// block: '',
// url: ''
//}
function work(options){
window.d = document
var buffer = ''
d.write = d.writeln = function(s){ buffer += s }
d.open = d.close = function(){}
s = d.createElement('script')
s.setAttribute('type','text/javascript')
//s.setAttribute('src',CONFIG.url)
//d.getElementById(CONFIG.block).appendChild(s)
s.setAttribute('src',options.url)
d.getElementById(options.block).appendChild(s)
s.onload = function () {
window.setTimeout(function() {
//console.warn(CONFIG.block + ' ' + buffer)
//d.getElementById(CONFIG.block).innerHTML += buffer
console.warn(options.block + ' ' + buffer)
d.getElementById(options.block).innerHTML += buffer
buffer = ''
}, 0)
}
}
return {
init: function (options) {
var CONFIG = {
block: '',
url: ''
}
$.extend(CONFIG, options);
random = $('#'+CONFIG.block).attr('rel')
id = $('#'+CONFIG.block).attr('id').replace(random,'')
id = id.replace('DIV','')
size = id.split('X')
ele_width = size[0] || CONFIG.width
ele_height = size[1] || CONFIG.height
$('#'+CONFIG.block).css({
'width':ele_width+'px',
'height':ele_height+'px',
'background':'url(/static/i/ajax-loading-black.gif) no-repeat center center'
})
console.log(CONFIG.block)
$(window).load(function(){
//work()
work(CONFIG)
})
}
}
})();
How can I detect support for WebP via Javascript? I'd like to use feature detection rather than browser detection if possible, but I can't find a way to do so. Modernizr (www.modernizr.com) doesn't check for it.
This is my solution - is taking around 6ms and I'm considering WebP is only a feature for a modern browser. Uses a different approach using canvas.toDataUrl() function instead of image as the way to detect the feature:
function support_format_webp()
{
var elem = document.createElement('canvas');
if (!!(elem.getContext && elem.getContext('2d')))
{
// was able or not to get WebP representation
return elem.toDataURL('image/webp').indexOf('data:image/webp') == 0;
}
else
{
// very old browser like IE 8, canvas not supported
return false;
}
}
I think something like this might work:
var hasWebP = false;
(function() {
var img = new Image();
img.onload = function() {
hasWebP = !!(img.height > 0 && img.width > 0);
};
img.onerror = function() {
hasWebP = false;
};
img.src = 'http://www.gstatic.com/webp/gallery/1.webp';
})();
In Firefox and IE, the "onload" handler just won't be called at all if the image can't be understood, and the "onerror" is called instead.
You didn't mention jQuery, but as an example of how to deal with the asynchronous nature of that check you could return a jQuery "Deferred" object:
function hasWebP() {
var rv = $.Deferred();
var img = new Image();
img.onload = function() { rv.resolve(); };
img.onerror = function() { rv.reject(); };
img.src = 'http://www.gstatic.com/webp/gallery/1.webp';
return rv.promise();
}
Then you could write:
hasWebP().then(function() {
// ... code to take advantage of WebP ...
}, function() {
// ... code to deal with the lack of WebP ...
});
Here is a jsfiddle example.
A more advanced checker: http://jsfiddle.net/JMzj2/29/. This one loads images from a data URL and checks whether it loads successfully. Since WebP now also supports lossless images, you could check whether the current browser supports just lossy WebP or also lossless WebP. (Note: This implicitly also checks for data URL support.)
var hasWebP = (function() {
// some small (2x1 px) test images for each feature
var images = {
basic: "data:image/webp;base64,UklGRjIAAABXRUJQVlA4ICYAAACyAgCdASoCAAEALmk0mk0iIiIiIgBoSygABc6zbAAA/v56QAAAAA==",
lossless: "data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAQAAAAfQ//73v/+BiOh/AAA="
};
return function(feature) {
var deferred = $.Deferred();
$("<img>").on("load", function() {
// the images should have these dimensions
if(this.width === 2 && this.height === 1) {
deferred.resolve();
} else {
deferred.reject();
}
}).on("error", function() {
deferred.reject();
}).attr("src", images[feature || "basic"]);
return deferred.promise();
}
})();
var add = function(msg) {
$("<p>").text(msg).appendTo("#x");
};
hasWebP().then(function() {
add("Basic WebP available");
}, function() {
add("Basic WebP *not* available");
});
hasWebP("lossless").then(function() {
add("Lossless WebP available");
}, function() {
add("Lossless WebP *not* available");
});
Official way by Google:
Since some old browsers have partial support for webp, so it is better to be more specific which webp feature you are trying to use & detect this specific feature, and here is Google's official recommendation for how to detect a specific webp feature:
// check_webp_feature:
// 'feature' can be one of 'lossy', 'lossless', 'alpha' or 'animation'.
// 'callback(feature, isSupported)' will be passed back the detection result (in an asynchronous way!)
function check_webp_feature(feature, callback) {
var kTestImages = {
lossy: "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA",
lossless: "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==",
alpha: "UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",
animation: "UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA"
};
var img = new Image();
img.onload = function () {
var result = (img.width > 0) && (img.height > 0);
callback(feature, result);
};
img.onerror = function () {
callback(feature, false);
};
img.src = "data:image/webp;base64," + kTestImages[feature];
}
Example Usage:
check_webp_feature('lossy', function (feature, isSupported) {
if (isSupported) {
// webp is supported,
// you can cache the result here if you want
}
});
Note that image-loading is non-blocking and asynchronous. This means that any code that depends on WebP support should preferably be put in the callback function.
Also note that other synchronous solutions won't work well with Firefox 65
Preferred solution in HTML5
<picture>
<source srcset="/path/to/image.webp" type="image/webp">
<img src="/path/to/image.jpg" alt="insert alt text here">
</picture>
Wiki on W3C
This is an old question, but Modernizr now supports Webp detection.
http://modernizr.com/download/
Look for img-webp under Non-core detects.
Here's a version of James Westgate's answer in ES6.
function testWebP() {
return new Promise(res => {
const webP = new Image();
webP.src = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA';
webP.onload = webP.onerror = () => {
res(webP.height === 2);
};
})
};
testWebP().then(hasWebP => console.log(hasWebP));
FF64: false
FF65: true
Chrome: true
I love the synchronous answer from Rui Marques, but unfortunately FF65 still returns false despite having the ability to display WebP.
Here is code without having to request an image. Updated with qwerty's new fiddle.
http://jsfiddle.net/z6kH9/
function testWebP(callback) {
var webP = new Image();
webP.onload = webP.onerror = function () {
callback(webP.height == 2);
};
webP.src = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA';
};
testWebP(function(support) {
document.body.innerHTML = support ? 'Yeah man!' : 'Nope';
});
WebPJS uses smarter WebP support detection with no external images required:
http://webpjs.appspot.com/
I've found webp support feature detect requires 300+ms when the page is JavaScript heavy. So I wrote a script with caching features:
script cache
localstorage cache
It will only detect once when user first accessing the page.
/**
* #fileOverview WebP Support Detect.
* #author ChenCheng<sorrycc#gmail.com>
*/
(function() {
if (this.WebP) return;
this.WebP = {};
WebP._cb = function(isSupport, _cb) {
this.isSupport = function(cb) {
cb(isSupport);
};
_cb(isSupport);
if (window.chrome || window.opera && window.localStorage) {
window.localStorage.setItem("webpsupport", isSupport);
}
};
WebP.isSupport = function(cb) {
if (!cb) return;
if (!window.chrome && !window.opera) return WebP._cb(false, cb);
if (window.localStorage && window.localStorage.getItem("webpsupport") !== null) {
var val = window.localStorage.getItem("webpsupport");
WebP._cb(val === "true", cb);
return;
}
var img = new Image();
img.src = "data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA";
img.onload = img.onerror = function() {
WebP._cb(img.width === 2 && img.height === 2, cb);
};
};
WebP.run = function(cb) {
this.isSupport(function(isSupport) {
if (isSupport) cb();
});
};
})();
/* Here's a one-liner hack that works (without the use/need of any
externals...save bytes)...
Your CSS... */
body.no-webp .logo {
background-image: url('logo.png');
}
body.webp .logo {
background-image: url('logo.webp');
}
...
<body>
<!--
The following img tag is the *webp* support checker. I'd advise you use any
(small-sized) image that would be utilized on the current page eventually
(probably an image common to all your pages, maybe a logo) so that when
it'll be (really) used on the page, it'll be loaded from cache by the
browser instead of making another call to the server (for some other image
that won't be).
Sidebar: Using 'display: none' so it's not detected by screen readers and
so it's also not displayed (obviously). :)
-->
<img
style='display: none'
src='/path/to/low-sized-image.webp'
onload="this.parentNode.classList.add('webp')"
onerror="this.parentNode.classList.add('no-webp')"
/>
...
</body>
<!-- PS. It's my first answer on SO. Thank you. :) -->
WebP images with htaccess
Place the following in your .htaccess file and jpg/png images will be replaced with WebP images if found in the same folder.
<IfModule mod_rewrite.c>
RewriteEngine On
# Check if browser support WebP images
RewriteCond %{HTTP_ACCEPT} image/webp
# Check if WebP replacement image exists
RewriteCond %{DOCUMENT_ROOT}/$1.webp -f
# Serve WebP image instead
RewriteRule (.+)\.(jpe?g|png)$ $1.webp [T=image/webp,E=accept:1]
</IfModule>
<IfModule mod_headers.c>
Header append Vary Accept env=REDIRECT_accept
</IfModule>
<IfModule mod_mime.c>
AddType image/webp .webp
</IfModule>
Read more here
here is a simple function with Promise based on Pointy's response
let webpSupport = undefined // so we won't have to create the image multiple times
const webp1Px = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA'
function isWebpSupported () {
if (webpSupport !== undefined) {
return Promise.resolve(webpSupport)
}
return new Promise((resolve, _reject) => {
const img = new Image()
img.onload = () => {
webpSupport = !!(img.height > 0 && img.width > 0);
resolve(webpSupport)
}
img.onerror = () => {
webpSupport = false
resolve(webpSupport)
}
img.src = webp1Px
})
}
My short version. I'm used it to give browser webP or jpg/png.
Google eat this, and old iphone ( f̶u̶c̶k̶i̶n̶g̶ ̶s̶h̶e̶e̶t̶ -safari) work great too!
function checkWebP(callback) {
var webP = new Image();
webP.onload = webP.onerror = function () {
callback(webP.height == 2);
};
webP.src = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA';
};
checkWebP(function(support) {
if(support) {
//Do what you whant =)
console.log('work webp');
}else{
//Do what you whant =)
console.log('not work, use jgp/png')
}
})
This is a hybrid HTML/Javascript method that will let you determine supported image types in order of preference (your preference). In this example it will return the first supported image type in the browser and checks AVIF, WebP, JpegXL and JPG.
<picture style="display:none;">
<source type=image/avif srcset="data:image/avif;base64,AAAAFGZ0eXBhdmlmAAAAAG1pZjEAAACgbWV0YQAAAAAAAAAOcGl0bQAAAAAAAQAAAB5pbG9jAAAAAEQAAAEAAQAAAAEAAAC8AAAAGwAAACNpaW5mAAAAAAABAAAAFWluZmUCAAAAAAEAAGF2MDEAAAAARWlwcnAAAAAoaXBjbwAAABRpc3BlAAAAAAAAAAQAAAAEAAAADGF2MUOBAAAAAAAAFWlwbWEAAAAAAAAAAQABAgECAAAAI21kYXQSAAoIP8R8hAQ0BUAyDWeeUy0JG+QAACANEkA= 1x">
<source type=image/webp srcset="data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA 1x">
<source type=image/jxl srcset="data:image/jxl;base64,/woAEBAJCAQBACwASxLFgoUJEP3D/wA= 1x">
<img onload=console.log(this.currentSrc.substring(this.currentSrc.indexOf(':')+1,this.currentSrc.indexOf(';'))) src="data:image/jpg;base64,/9j/4AAQSkZJRgABAQIAHAAcAAD/2wBDAAMDAwMDAwMDAwMEBAQEBAYFBQUFBgkGBwYHBgkOCAoICAoIDgwPDAsMDwwWEQ8PERYZFRQVGR4bGx4mJCYyMkP/wgALCAABAAEBAREA/8QAFAABAAAAAAAAAAAAAAAAAAAACf/aAAgBAQAAAABU/wD/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAE/AH//2Q==">
</picture>
You can replace the log function with whatever you need.
Benefits of this approach will be:
you don't have to create and query a bunch of objects in Javascript so it is efficient
The browser doesn't have to fetch any images, they are encoded inline, so it is fast and synchronous. You can stick this in anywhere and have the answer in the next line without callbacks.
The browser will only create an image result for the first supported line, so it is efficient.
It's easy to add future image support by adding one line.
You can order the images for whatever priority you will be using in your application.
You can turn this into individual tests by pruning image types you don't care about.
This should work even when the PICTURE element is not supported, but requires currentSrc, so IE11 will fail.. in which case just test for currentSrc in img or else assume JPG support is baked in always.
EDIT: removed the line break that got into the example, thanks.
There is a way to test webP support instantly.
It's sync and accurate, so there is no need to wait for a callback to render images.
function testWebP = () => {
const canvas = typeof document === 'object' ?
document.createElement('canvas') : {};
canvas.width = canvas.height = 1;
return canvas.toDataURL ? canvas.toDataURL('image/webp').indexOf('image/webp') === 5 : false;
}
This method improved my rendering time dramatically
Webp extension Detect And Replacement JavaScript:
async function supportsWebp() {
if (!self.createImageBitmap) return false;
const webpData = 'data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=';
const blob = await fetch(webpData).then(r => r.blob());
return createImageBitmap(blob).then(() => true, () => false);
}
(async () => {
if(await supportsWebp()) {
console.log('webp does support');
}
else {
$('#banners .item').each(function(){
var src=$(this).find('img').attr('src');
src = src.replace(".webp", ".jpg");
$(this).find('img').attr('src',src);
});
console.log('webp does not support');
}
})();
Improved version to handle Firefox based on Rui Marques. I added the scan for the different strings based on comments to that answer.
If this improvement is accepted by the community, it should be edited in to that answer.
function canUseWebP()
{
var elem = document.createElement('canvas');
if (!!(elem.getContext && elem.getContext('2d')))
{
var testString = (!(window.mozInnerScreenX == null)) ? 'png' : 'webp';
// was able or not to get WebP representation
return elem.toDataURL('image/webp').indexOf('data:image/' + testString) == 0;
}
// very old browser like IE 8, canvas not supported
return false;
}
Great news. It works in Safari.
document.addEventListener('DOMContentLoaded', function() {
testWebP(document.body)
})
function testWebP(elem) {
const webP = new Image();
webP.src = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA';
webP.onload = webP.onerror = function () {
webP.height === 2 ? elem.classList.add('webp-true') : elem.classList.add('webp-false')
}
console.log(webP)
}
A source: https://gist.github.com/Protoff/d6643387f03d47b44b2d7c3cf7b3e0a0
Using #Pointy's answer this is for Angular 2+:
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class ImageService {
private isWebpEnabledSource = new Subject<boolean>();
isWebpEnabledAnnounced$ = this.isWebpEnabledSource.asObservable();
isWebpEnabled() {
let webpImage = new Image();
webpImage.src = 'data:image/webp;base64,UklGRjIAAABXRUJQVlA4ICYAAACyAgCdASoCAAEALmk0mk0iIiIiIgBoSygABc6zbAAA/v56QAAAAA==';
webpImage.onload = () => {
if (webpImage.width === 2 && webpImage.height === 1) {
this.isWebpEnabledSource.next(true);
} else {
this.isWebpEnabledSource.next(false);
}
}
}
}
The above solutions may not work in safari and firefox. So I started looking for a more robust solution and stumbled upon a great library about webp support: webp-hero We can take only detectWebpSupport function from this library:
var __awaiter = (this && this.__awaiter) || function(thisArg, _arguments, P, generator) {
function adopt(value) {
return value instanceof P ? value : new P(function(resolve) {
resolve(value);
});
}
return new(P || (P = Promise))(function(resolve, reject) {
function fulfilled(value) {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
}
function rejected(value) {
try {
step(generator["throw"](value));
} catch (e) {
reject(e);
}
}
function step(result) {
result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
}
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function(thisArg, body) {
var _ = {
label: 0,
sent: function() {
if (t[0] & 1) throw t[1];
return t[1];
},
trys: [],
ops: []
},
f, y, t, g;
return g = {
next: verb(0),
"throw": verb(1),
"return": verb(2)
}, typeof Symbol === "function" && (g[Symbol.iterator] = function() {
return this;
}), g;
function verb(n) {
return function(v) {
return step([n, v]);
};
}
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0:
case 1:
t = op;
break;
case 4:
_.label++;
return {
value: op[1],
done: false
};
case 5:
_.label++;
y = op[1];
op = [0];
continue;
case 7:
op = _.ops.pop();
_.trys.pop();
continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
_ = 0;
continue;
}
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
_.label = op[1];
break;
}
if (op[0] === 6 && _.label < t[1]) {
_.label = t[1];
t = op;
break;
}
if (t && _.label < t[2]) {
_.label = t[2];
_.ops.push(op);
break;
}
if (t[2]) _.ops.pop();
_.trys.pop();
continue;
}
op = body.call(thisArg, _);
} catch (e) {
op = [6, e];
y = 0;
} finally {
f = t = 0;
}
if (op[0] & 5) throw op[1];
return {
value: op[0] ? op[1] : void 0,
done: true
};
}
};
function detectWebpSupport() {
return __awaiter(this, void 0, void 0, function() {
var testImageSources, testImage, results;
return __generator(this, function(_a) {
switch (_a.label) {
case 0:
testImageSources = [
"data:image/webp;base64,UklGRjIAAABXRUJQVlA4ICYAAACyAgCdASoCAAEALmk0mk0iIiIiIgBoSygABc6zbAAA/v56QAAAAA==",
"data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAQAAAAfQ//73v/+BiOh/AAA="
];
testImage = function(src) {
return new Promise(function(resolve, reject) {
var img = document.createElement("img");
img.onerror = function(error) {
return resolve(false);
};
img.onload = function() {
return resolve(true);
};
img.src = src;
});
};
return [4 /*yield*/ , Promise.all(testImageSources.map(testImage))];
case 1:
results = _a.sent();
return [2 /*return*/ , results.every(function(result) {
return !!result;
})];
}
});
});
}
detectWebpSupport().then(d => console.log('does it support?', d))
//* WebP support checking
import { useState, useEffect } from "react";
const WebpSupportCheck = (feature, callback) => {
var kTestImages = {
lossy: "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA",
lossless: "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==",
alpha: "UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",
animation: "UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA"
};
var img = new Image();
img.onload = function () {
var result = (img.width > 0) && (img.height > 0);
callback(feature, result);
};
img.onerror = function () {
callback(feature, false);
};
img.src = "data:image/webp;base64," + kTestImages[feature];
}
const IsWebpSupported = () => {
const [state, setState] = useState()
useEffect(() => {
WebpSupportCheck('lossy', function (feature, isSupported) {
if (isSupported) {
setState(true)
} else {
setState(false)
}
})
}, [state])
return state
}
export default IsWebpSupported