131

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.

Gant
  • 29,661
  • 6
  • 46
  • 65
dieki
  • 2,435
  • 5
  • 23
  • 29
  • 2
    If you load such an image into an Image element, and then check width and height in a browser that **doesn't** support the format, do you get anything? – Pointy Apr 06 '11 at 21:39
  • (I meant "Image **object**", not element; like, "new Image()" ... ) – Pointy Apr 06 '11 at 21:47
  • Looks good. I can get a "I do support WebP" this way; but I can't get a "I do not support WebP". – dieki Apr 06 '11 at 22:00
  • 4
    I have posted a similar question: [What is Google's "official" recommendation for detecting WebP browser support?](https://groups.google.com/a/webmproject.org/forum/#!msg/webp-discuss/6nYUpcSAORs/1bf1lMi6Ki0J) on the WebP Google group. – Mike Jan 31 '13 at 15:28
  • This is better: http://queryj.wordpress.com/2012/06/11/detecting-webp-support/ – user956584 May 15 '14 at 16:19
  • @Mike There is an official [Google Dev Note](https://developers.google.com/speed/webp/faq#how_can_i_detect_browser_support_using_javascript) for this now. – hexalys May 29 '14 at 01:15
  • If you'd like Firefox to implement native WebP, you could [vote for this issue in their bug tracker](https://bugzilla.mozilla.org/page.cgi?id=voting/user.html&bug_id=856375#vote_856375). – Blaise Jul 19 '14 at 07:34
  • These answers are absolutely nuts. This is the modern web is it?! – Simon_Weaver Jun 26 '18 at 00:01
  • 3
    @Simon_Weaver the question and comments are all several years old. Old questions are rarely "maintained" in any significant way; you're always free to add a new answer however. – Pointy Oct 08 '18 at 09:34
  • I don't know if it's new, but modernizer check for it `now`: https://modernizr.com/download?webp-setclasses&q=webp – suther Dec 30 '20 at 16:14

22 Answers22

169

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;
 }
}
John
  • 1
  • 13
  • 98
  • 177
Rui Marques
  • 3,429
  • 1
  • 22
  • 26
  • Great! Was using it for canvas anyway, so this is a fast + useful test. – Josiah Mar 02 '15 at 17:36
  • 1
    This is hands down the best solution. If you are a npm modules kinda guy (or girl), I published a shortened version on this as [`supports-webp`](https://npmjs.com/package/supports-webp) – fregante Aug 04 '16 at 14:09
  • 10
    This should be the accepted answer, because all the others has a delay because of pointing to a network resource or a data URI – Imal Hasaranga Perera Mar 02 '17 at 12:15
  • 2
    You should set width and height 1px or toDataURL will be very long in Chrome and Opera. – vitaliydev Jun 22 '17 at 14:37
  • 11
    Simplified version: `webp = e => document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') == 0;` – António Almeida Mar 26 '18 at 12:04
  • this is brilliant – oblalex Dec 08 '18 at 23:53
  • 61
    This doesn't work in Firefox 65 which supports displaying webp, but not creating a webp data url from a canvas element. – carlcheel Dec 13 '18 at 13:04
  • 5
    Love this because it's synchronous, however @carlcheel is correct. FF 65 (that does have webp support) still returns false here :-( – RoccoB Jan 10 '19 at 01:08
  • 18
    This **would** be great if it worked for FF65 and Edge18. They both support webp but serialize the canvas with "data:image/png" – undefinederror Jan 11 '19 at 11:42
  • 7
    As of macOS BigSur, that adds support for WebP in Safari, this unfortunately also doesn't work in Safari, just like FF65+ the `toDataURL('image/webp')` produces a PNG. – duncanwilcox Jul 27 '20 at 20:15
  • 5
    I looks like this only works on Chrome, this is not very useful... – Maxime Chéramy Nov 16 '20 at 15:06
  • 2
    this doesn't work with Safari 14. Safari 14 supports webp but this function returns false – Ivan Jul 06 '21 at 08:12
  • 2
    @Rui - You mentioned this took about 6ms, you can get it down to 1ms by setting the width and height of the canvas to 1 before encoding. For those concerned about safari- TMK some versions of safari which are still widely used (15.?) have webp support but it fails for some images. It was fixed in a later release. For now, I end up skipping webp on Safari in general. – Spencer Evans Aug 04 '21 at 05:01
  • this method doesn't work on firefox. check out my answer https://stackoverflow.com/a/69223893/10698741 – doğukan Sep 17 '21 at 13:56
  • does this work in mobile? it returns false to me in all my browser on an iphone – Marc Apr 05 '22 at 08:27
  • 2
    It doesn't work for safari, because toDataUrl type 'image/webp' is not supported. But safari can show webp images. So this solution is wrong and not the answer. https://caniuse.com/mdn-api_htmlcanvaselement_todataurl_type_parameter_webp – Michal Kalita Jan 11 '23 at 12:18
  • iOS 16 safari returns false – jjxtra Jan 18 '23 at 22:31
56

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

AbdelHady
  • 9,334
  • 8
  • 56
  • 83
56

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");
});
pimvdb
  • 151,816
  • 78
  • 307
  • 352
Pointy
  • 405,095
  • 59
  • 585
  • 614
  • Awesome! This works in FF, Chrome, and IE 9. For some reason it isn't working in IE8 or IE7. – dieki Apr 06 '11 at 22:02
  • It works for me in IE7 - try the jsFiddle I just linked to the end of the answer. – Pointy Apr 06 '11 at 22:03
  • Well my original answer just had the "onload" - I didn't know there even was an "onerror" for Image objects :-) – Pointy Apr 06 '11 at 22:10
  • Oh, duh. I used a data: url instead of an image, and IE7 doesn't support that. :P – dieki Apr 06 '11 at 22:13
  • Oh well that'd do it :-) I'm a little surprised it didn't bother to call the error handler, but then we're talking about IE7 and it's somewhat amazing when it manages to do anything at all. – Pointy Apr 06 '11 at 22:14
  • Well, this answers my original question. But do you have any ideas on detecting this with pure javascript, without the need for the image? That's what I was trying to do with the data: url. – dieki Apr 06 '11 at 22:23
  • Hmm well my example pulls the real image from Google, and since it's their format they might not mind :-) But no, since the IE's won't understand data URLs, and of course they don't have any way to explicitly tell you they don't know about something invented since they were released, I can't think of any way to do it than by directly trying an image. It's *possible* that those browsers will cache the image data even though they can't make an image from it ... – Pointy Apr 06 '11 at 22:43
  • 3
    I just realized that I can get IE8 to work properly with data: urls, and get IE7 to throw an error for it; the problem was that they don't support them directly in javascript. See: http://jsfiddle.net/HaLXz/ – dieki Apr 06 '11 at 22:54
  • Hm, alright. I wanted to write a patch to add support to Modernizr, but it has to be JS-only for that. :) – dieki Apr 06 '11 at 22:55
  • @Pointy: Your deferred code could be tuned quite a bit to only "detect" once (as opposed to every call) by memoizing itself (so it always returns the same promise, instead of generating a new one every time). Just a thought... – ircmaxell May 31 '13 at 17:04
  • @ircmaxell yes that's true; or it could be incorporated into something like Modernizr that would do the memoizing itself. – Pointy May 31 '13 at 17:25
  • 1
    This doesn't seem to be working for Safari on OSX. The below answer does though. – Sam Tolton Jun 17 '19 at 13:54
  • @SamTolton well this answer is *eight years old* :) – Pointy Jun 17 '19 at 14:25
52

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

Andrei Krasutski
  • 4,913
  • 1
  • 29
  • 35
  • 4
    Works perfectly, the `type="image/webp"` is critical in order for browser to skip it if unknown format ! – adrianTNT Nov 14 '18 at 10:05
  • 13
    This is good, but it doesn't work for background images, and if you are retrofitting webp to a site, and it requires modifying your html, which then also means modifying all the places in your css that reference the img tag. – kloddant Jan 06 '20 at 16:23
  • 2
    Yes this is the best solution for the problem. Thanks – Janith Feb 28 '20 at 08:17
  • Do all browsers that support webp support the picture-element as well? – molerat Jul 28 '20 at 11:04
  • 2
    Though the native HTML5 is preferable, most browsers load both the JPG and WEBP, which negatively impacts download times. In depth: https://www.smashingmagazine.com/2013/05/how-to-avoid-duplicate-downloads-in-responsive-images/ – Jan Werkhoven Nov 22 '20 at 01:25
22

This is an old question, but Modernizr now supports Webp detection.

http://modernizr.com/download/

Look for img-webp under Non-core detects.

Jake Wilson
  • 88,616
  • 93
  • 252
  • 370
  • 3
    The source is useful to see what they did https://github.com/Modernizr/Modernizr/blob/400db4043c22af98d46e1d2b9cbc5cb062791192/feature-detects/img/webp.js – Simon_Weaver Jun 26 '18 at 00:00
  • For me it has worked quite reliably and it allows you to work with css classes .webp and .no-webp for more flexibility. – molerat Jul 28 '20 at 11:03
  • Had to use some custom checking instead of modernizr since I could not find a way how to make it work on modern stack like angular cli (even with ngx-build-plus) – Zdenek Hatak Nov 18 '21 at 14:38
18

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.

RoccoB
  • 2,439
  • 2
  • 27
  • 30
12

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';
});
James Westgate
  • 11,306
  • 8
  • 61
  • 68
  • 5
    That was completely broken for me. I forked it and made it work: http://jsfiddle.net/z6kH9/ – qwerty Jun 28 '14 at 11:41
  • Will this work in all browsers? I'm referring to the issues of other solutions in Safari + FF65+. – molerat Jul 28 '20 at 10:26
  • I think this one is the best solution. Still would like to see the tests on older browsers, and browsers that are not supporting webp. – Kostanos Oct 11 '20 at 21:28
5

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();
    });
  };

})();
sorrycc
  • 111
  • 1
  • 3
5

/* 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. :) -->
5

WebPJS uses smarter WebP support detection with no external images required: http://webpjs.appspot.com/

unxed
  • 196
  • 1
  • 3
  • 6
  • The script that they use to load their file can be used without actually using their file. Just replace the inside with whatever you would like to do if there is no WebP support. – tgrosinger Nov 29 '11 at 16:50
  • 1
    It looks like they're using data urls, with is what I ended up using. – dieki Jan 16 '12 at 01:43
2

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

Andrei Krasutski
  • 4,913
  • 1
  • 29
  • 35
2

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
  })
}
Liron Navon
  • 1,068
  • 1
  • 11
  • 22
  • While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. – Nic3500 Aug 05 '18 at 13:46
  • I thought it's pretty clear, we try to load a webp image from base64 string (which is 1px wide and high), if we loaded it properly (onload called) it's supported, if not (onerror called) it's not, I simply wrapped it in a promise. – Liron Navon Jun 24 '19 at 13:16
2

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')
      }
      
})
Alexander Sanik
  • 168
  • 1
  • 8
2

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:

  1. you don't have to create and query a bunch of objects in Javascript so it is efficient
  2. 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.
  3. The browser will only create an image result for the first supported line, so it is efficient.
  4. It's easy to add future image support by adding one line.
  5. You can order the images for whatever priority you will be using in your application.
  6. You can turn this into individual tests by pruning image types you don't care about.
  7. 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.

dbquarrel
  • 1,446
  • 1
  • 10
  • 8
1

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

Guy Sopher
  • 4,402
  • 4
  • 22
  • 36
  • 6
    doesn't work on Firefox... which supports `image/webp` but returns false in this case (but works on both Safari and Chrome correctly) – a14m Mar 11 '19 at 13:48
1

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');
  }
})();
Limitless isa
  • 3,689
  • 36
  • 28
1

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;
}
Jeffrey Simon
  • 918
  • 3
  • 11
  • 25
1

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

0

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);
            }
        }
    }
}
Serj Sagan
  • 28,927
  • 17
  • 154
  • 183
0

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))
doğukan
  • 23,073
  • 13
  • 57
  • 69
0

//* 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
Ren
  • 1
0

Official Google version using async:

// check_webp_feature:
//   'feature' can be one of 'lossy', 'lossless', 'alpha' or 'animation'.
async function check_webp_feature(feature) {
  const kTestImages = {
    lossy: "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA",
    lossless: "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==",
    alpha: "UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",
    animation: "UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA",
  };
  const img = new Image();
  return new Promise(resolve => {
    img.addEventListener("load", () => resolve(img.width > 0 && img.height > 0));
    img.addEventListener("error", () => resolve(false));
    img.src = "data:image/webp;base64," + kTestImages[feature];
  });
}

Example usage:

if (await check_webp_feature("lossy")) {
  // webp is supported
});

Official version with more explanation:

https://developers.google.com/speed/webp/faq#in_your_own_javascript

Toxiro
  • 528
  • 1
  • 3
  • 12