使用 picture 标签预加载图像以防止闪烁

Preload of images to prevent flickering using the picture tag

在我的代码中有一张标签图片,我更改了它的内容以加载另一张图片。但我注意到闪烁。这是我的玩具代码。

setTimeout(replaceImage,3000);

function replaceImage(){
let pictureNode = document.getElementById('picture-carousel');

let markup = `
               <source media="(min-width:50em)" srcset="http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/debris2_blue.png">
                
                  <source media="(min-width:25em)" srcset="http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/debris2_brown.png"> 
                  <source  srcset="http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/debris3_blue.png"> 
                  <img src='http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/debris2_blue.png' title='Bla bla' alt='bla bla'>
`

pictureNode.innerHTML = markup;
}
<div>
<picture id='picture-carousel'>
                  <source media="(min-width:50em)" srcset="http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/asteroid_blend.png">
                
                  <source media="(min-width:25em)" srcset="http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/asteroid_blue.png"> 
                  <source  srcset="http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/asteroid_brown.png"> 
                  <img src='http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/asteroid_blend.png' title='Bla bla' alt='bla bla'> <!--  fallback -->
               </picture>
               </div>

我知道要解决图像闪烁问题,我可以像这样预加载图像:

function loadImage(){
var img = new Image(),
x=document.getElementById('myImg');
img.onload=function(){
  x.src = img.src;
}

img.src ='http://codeskulptor-demos.commondatastorage.googleapis.com/GalaxyInvaders/back03.jpg'; //  this is the new url img to load';
}
<img src='http://codeskulptor-demos.commondatastorage.googleapis.com/GalaxyInvaders/back02.jpg' id='myImg'>

<button onclick='loadImage();'>Load new Image</button>

当我使用 picture 标签而不是 img 标签时,如何预加载图像?

以数据的形式构建您的 <picture> 元素。这样我们就可以构建一个函数来接收数据并逐个构建元素。

执行此操作时,我们可以检查每个 <source> 标签的 media 值。使用该值,我们可以 运行 window.matchMedia() 确定元素上的媒体查询是否与当前视图匹配。例如,您可以检查 min-width: 50em 当前是否为真。

有了这些知识,您就可以确定要预加载的图像。您只需加载与媒体查询匹配的图像。

查看下面的示例,它执行上述过程。 如果您有任何问题,请联系我们。

const pictureData = [
  {
    tag: 'source',
    attributes: {
      media: '(min-width: 50em)',
      srcset: 'http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/debris2_blue.png'
    }
  },
  {
    tag: 'source',
    attributes: {
      media: '(min-width: 25em)',
      srcset: 'http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/debris2_brown.png'
    }
  },
  {
    tag: 'source',
    attributes: {
      srcset: 'http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/debris3_blue.png'
    }
  },
  {
    tag: 'img',
    attributes: {
      src: 'http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/debris2_blue.png',
      title: 'Bla bla',
      alt: 'bla bla'
    }
  }
];

/**
 * Loads an image and returns a promise.
 * The promise will resolve on the load event of the image.
 */
const preloadImage = url => new Promise((resolve, reject) => {
  const image = new Image();
  image.onload = () => resolve();
  image.onerror = error => reject(error);
  image.src = url;
})

/**
 * Takes in an object of pictureData.
 * It then creates a <picture> element and the children specified in the object.
 * Whenever a tag is source, it will use the media value, if present,
 * to detect if the image is elligable to be loaded. 
 * If so it will preload the image and resolve the promise after it has been loaded.
 */
const buildPicture = async pictureData => {
  const picture = document.createElement('picture');
  let imageToPreload = null;

  // Loop through all the data and start constructing.
  for (const { tag, attributes } of pictureData) {
    const child = document.createElement(tag);
    
    if (imageToPreload === null) {
      // Check source tags for media queries.
      if (tag === 'source') {
        const { media, srcset } = attributes;
        
        // If there is a media query, check it.
        if (!media) {
          const { matches } = window.matchMedia(media);

          // If the query matches, use this as the image.
          if (matches === true) {
            imageToPreload = srcset;
          }

        // No media query found.
        } else {
          imageToPreload = srcset;
        }
      }

      // If no match has been found yet, just load the image.
      if (tag === 'img') {
        const { src } = attributes;
        imageToPreload = src;
      }
    }
    
    // Set all the properties and values of the attributes.
    for (const [ property, value ] of Object.entries(attributes)) {
      child.setAttribute(property, value);
    }

    picture.append(child);
  }

  if (imageToPreload !== null) {
    try {
      await preloadImage(imageToPreload);
    } catch (error) {
      console.error(error);
    }
  }

  return picture;
};

setTimeout(async () => {
  const currentPicture = document.getElementById('picture-carousel');
  const picture = await buildPicture(pictureData);
  currentPicture.replaceWith(picture);
}, 2000);
<picture id='picture-carousel'>
  <source media="(min-width:50em)" srcset="http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/asteroid_blend.png">
  <source media="(min-width:25em)" srcset="http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/asteroid_blue.png">
  <source srcset="http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/asteroid_brown.png">
  <img src='http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/asteroid_blend.png' title='Bla bla' alt='bla bla'>
  <!--  fallback -->
</picture>