aFarkas / lazysizes

High performance and SEO friendly lazy loader for images (responsive and normal), iframes and more, that detects any visibility changes triggered through user interaction, CSS or JavaScript without configuration.
MIT License
17.54k stars 1.73k forks source link

Usage of Network information API #445

Open cxq opened 7 years ago

cxq commented 7 years ago

Hello, A new browser API is available since few time: http://wicg.github.io/netinfo/

We could be able to load a different quality of images depending on the user connection speed. If user is having 3G, we could serve a lighter image and vice-versa.

What do you think?

Thanks, Xiu

aFarkas commented 7 years ago

@cxq

Not 100% sure about this. Because this is a use case of the srcset attribute itself. Which means the browser should handle this case automatically. Maybe file a bug report here: https://crbug.com/wizard

In case you want to try it out, you can use the optimumx plugin and use the following configuration as a start (Please let me know if you come up with a better getOptimumX function).:

(function () {
    'use strict';

    function getNetInfo(){
        var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection || {};

        return {
            saveData: connection.saveData || false,
            download: connection.download || 10,
        };
    }

    function getOptimumX(/*element*/){
        var netinfo = getNetInfo();
        var dpr = window.devicePixelRatio || 1;
        var maxDpr = netinfo.saveData ?
            dpr > 1.4 ?
                1.2 :
                dpr - 0.05 :
            2;

        if(netinfo.download < 20 || netinfo.saveData){

            if(dpr > 2.6){
                dpr *= 0.6; // returns 1.8 for 3
            } else if(dpr > 1.9){
                dpr *= 0.8; // returns 1.6 for 2
            } else {
                dpr -= 0.01; // returns 0.99 for 1
            }

            if(netinfo.download < 2 || netinfo.saveData){
                dpr *= dpr > 1.5 ? 0.8 : 0.9;
            } else if(netinfo.download < 5 && dpr > 1.5){
                dpr *= 0.9;
            }
        }

        return Math.min(Math.round(dpr * 100) / 100, maxDpr);
    }

    window.lazySizesConfig = window.lazySizesConfig || {};
    window.lazySizesConfig.getOptimumX = getOptimumX;
    window.lazySizesConfig.constrainPixelDensity = true;
})();
charlespwd commented 4 years ago

I'm playing with this since there still doesn't seem to be any support from browsers coming anytime soon.

Note: It should be connection.downlink, connection.download does not exist in the spec.

charlespwd commented 4 years ago

Here's my stab at it:

/* global arguments */
/* eslint no-restricted-modules: ["error", { "patterns": ["*"] }] */
/* eslint no-restricted-imports: ["error", { "patterns": ["*"] }] */
/**
 * This file will be included by lazysizes. It is the first file loaded
 * and must execute really fast. This is why we dissalow imports here.
 **/

const memoizeOnce = fn => {
  let value;
  let lastArgs;
  return () => {
    if (!value || !lastArgs || lastArgs[0] !== arguments[0]) {
      lastArgs = [...arguments];
      value = fn(...lastArgs);
    }
    return value;
  };
};

const EFFECTIVE_TYPES = {
  'slow-2g': 0,
  '2g': 1,
  '3g': 2,
  '4g': 3,
};

// Use this for debugging/testing.
const getItem = k => JSON.parse(localStorage.getItem(k));
const getConnection = () =>
  navigator.connection || navigator.mozConnection || navigator.webkitConnection || {};

export function getNetInfo() {
  const connection = getConnection();
  const localSaveData = getItem('lazySizes.saveData');
  const localEffectiveType = getItem('lazySizes.effectiveType');
  const effectiveTypeId =
    localEffectiveType || // For QA/Testing
    connection.effectiveType || // For browsers that support it (Chrome, FirefoxForAndroid, Lighthouse)
    '4g'; // Other browsers not supported :( fallback to default

  return {
    saveData: localSaveData || connection.saveData || false, // if user asked for less data usage on the user agent.
    effectiveType: EFFECTIVE_TYPES[effectiveTypeId],
  };
}

const getMaxDpr = (saveData, dpr) => {
  if (saveData && dpr > 1.4) {
    return 1.2;
  } else if (saveData) {
    return dpr - 0.05;
  }
  return dpr;
};

function getOptimumX() {
  const netinfo = getNetInfo();
  let dpr = window.devicePixelRatio || 1;
  const ABSOLUTE_MAX_DPR = 2; // bit.ly/retina-capping
  const maxDpr = getMaxDpr(netinfo.saveData, Math.min(dpr, ABSOLUTE_MAX_DPR));

  // OK, I agree this looks like a bunch of magic numbers. I got those from
  // https://github.com/aFarkas/lazysizes/issues/445#issuecomment-342803941
  // The idea is to always have > 1 for higher dpi displays but still bring
  // it down a bit if the user exhibits slow download speed or if the user
  // asked for reduced data usage.
  if (netinfo.effectiveType < EFFECTIVE_TYPES['4g'] || netinfo.saveData) {
    if (dpr > 2.6) {
      dpr *= 0.6; // returns 1.8 for 3
    } else if (dpr > 1.9) {
      dpr *= 0.8; // returns 1.6 for 2
    } else {
      dpr -= 0.01; // returns 0.99 for 1
    }

    // If speed is really slow, kick it down a bit more.
    if (netinfo.effectiveType < EFFECTIVE_TYPES['3g']) {
      dpr *= dpr > 1.5 ? 0.8 : 0.9;
    }
  }

  return Math.min(Math.round(dpr * 100) / 100, maxDpr);
}

export default memoizeOnce(getOptimumX);
yellow1912 commented 3 years ago

This is interesting, but the use of localStorage etc makes thing over-complicated in my opinion, is it really that expensive to retrieve that information once per page load?

@aFarkas I think you meant connection.downlink, because there is no download property.

Also, check it against any number more than 10 can be useless, MDN states that

Note that Chrome-based browsers do not conform to the specification, and arbitrarily cap the reported downlink at a maximum of 10 Mbps as an anti-fingerprinting measure. Similar caps exist for the reported latency.

That said, I think a number greater than 2 is acceptable for a normal website load? Perhaps I'm so used to the terrible internet connection here?