การโหลดภาพล่วงหน้าด้วย JavaScript


263

ฟังก์ชั่นที่ฉันเขียนด้านล่างเพียงพอที่จะโหลดภาพล่วงหน้าเป็นส่วนใหญ่หรือไม่ถ้าไม่ทั้งหมดเบราว์เซอร์ที่ใช้กันทั่วไปในปัจจุบัน

function preloadImage(url)
{
    var img=new Image();
    img.src=url;
}

ฉันมีอาร์เรย์ของ URL ภาพที่ฉันวนซ้ำและเรียกใช้preloadImageฟังก์ชันสำหรับแต่ละ URL


19
โปรดทราบว่าเบราว์เซอร์ (ทั้งหมด?) จะปล่อยภาพหลังจากนั้นไม่กี่วินาทีหากคุณไม่ได้ใช้ เพื่อหลีกเลี่ยงปัญหานี้ให้อ้างอิงถึงimgวัตถุเช่นในอาร์เรย์ในขอบเขตหลัก
Tamlyn

3
คุณหมายถึงอะไรโดย "ปล่อยภาพ" ถ้าเบราว์เซอร์ถูกแคชไว้เบราว์เซอร์จะอยู่ที่นั่นใช่ไหม
Francisc

56
สั้นลงเล็กน้อย:(new Image()).src = url;
mrzmyr

20
โปรดทราบว่าสิ่งนี้จะไม่ทำงานเมื่อ Chrome devtool เปิดอยู่และ 'ปิดการใช้งาน chache' ถูกเปิดใช้งานภายในแผงเครือข่าย
Way

6
ยิ่งสั้นลง:new Image().src = url;
Daniel X Moore

คำตอบ:


163

ใช่. สิ่งนี้ควรใช้กับเบราว์เซอร์หลัก ๆ ทั้งหมด


48
ผู้ดำเนินรายการหมายเหตุ:โปรดหยุดการตั้งค่าสถานะนี้ว่า "ไม่ใช่คำตอบ" ในความเป็นจริงแล้วมันเป็นคำตอบที่ตรงกับคำถามที่ถูกถาม หากคุณคิดว่าคำตอบคือผิดหรือได้รับการสนับสนุนไม่เพียงพอมีหลักฐานแล้วdownvoteมัน
Cody Gray

43

ลองนี้ฉันคิดว่านี่จะดีกว่า

var images = [];
function preload() {
    for (var i = 0; i < arguments.length; i++) {
        images[i] = new Image();
        images[i].src = preload.arguments[i];
    }
}

//-- usage --//
preload(
    "http://domain.tld/gallery/image-001.jpg",
    "http://domain.tld/gallery/image-002.jpg",
    "http://domain.tld/gallery/image-003.jpg"
)

ที่มา: http://perishablepress.com/3-ways-preload-images-css-javascript-ajax/


3
ไม่มีonloadตัวจัดการสำหรับรูปภาพใด ๆ
Benny

12
คุณช่วยอธิบายได้ไหมว่าทำไมสิ่งนี้ถึงดีกว่า
JJJ

2
@BeNice ฉันคิดว่าคุณเข้าใจผิดการโหลดภาพนั้นเป็นแบบอะซิงก์ดังนั้นคุณต้องจัดการกับonloadรัฐหรือคุณแค่สร้างภาพในหน่วยความจำ
Benny

3
หากคุณใช้วิธีนี้อย่าลืมคำสั่ง var สำหรับ i-variable มิฉะนั้นจะถูกตั้งค่าทั่วโลกซึ่งอาจทำให้เกิดข้อผิดพลาดที่ยากมากที่จะแก้ไข (เว้นแต่คุณจะรู้ว่าคุณลืมคำสั่ง varfor (var i = 0.....
Mohammer

2
@Mohammer ขอบคุณสำหรับสิ่งนี้ฉันเพิ่งคัดลอกโค้ดจากลิงก์ที่ฉันให้ไว้ ฉันจะแก้ไขโค้ดตอนนี้เพื่อเพิ่มvar
clintgh

26

ในกรณีของฉันมันมีประโยชน์ในการเพิ่มการเรียกกลับไปยังฟังก์ชันของคุณสำหรับonloadเหตุการณ์:

function preloadImage(url, callback)
{
    var img=new Image();
    img.src=url;
    img.onload = callback;
}

และจากนั้นตัดเป็นกรณีของอาร์เรย์ของ URL ไปยังรูปภาพที่จะถูกโหลดไว้ล่วงหน้าด้วยการเรียกกลับที่ทำเสร็จแล้ว: https://jsfiddle.net/4r0Luoy7/

function preloadImages(urls, allImagesLoadedCallback){
    var loadedCounter = 0;
  var toBeLoadedNumber = urls.length;
  urls.forEach(function(url){
    preloadImage(url, function(){
        loadedCounter++;
            console.log('Number of loaded images: ' + loadedCounter);
      if(loadedCounter == toBeLoadedNumber){
        allImagesLoadedCallback();
      }
    });
  });
  function preloadImage(url, anImageLoadedCallback){
      var img = new Image();
      img.onload = anImageLoadedCallback;
      img.src = url;
  }
}

// Let's call it:
preloadImages([
    '//upload.wikimedia.org/wikipedia/commons/d/da/Internet2.jpg',
  '//www.csee.umbc.edu/wp-content/uploads/2011/08/www.jpg'
], function(){
    console.log('All images were loaded');
});

19

ทางเลือก CSS2: http://www.thecssninja.com/css/even-better-image-preloading-with-css2

body:after {
  content: url(img01.jpg) url(img02.jpg) url(img03.jpg);
  display: none; 
}

CSS3 Alternative: https://perishablepress.com/preload-images-css3/ (เขื่อน H / T Linh)

.preload-images {
  display: none; 
  width: 0;
  height: 0;
  background: url(img01.jpg),
              url(img02.jpg),
              url(img03.jpg);
}

หมายเหตุ: รูปภาพในคอนเทนเนอร์ที่display:noneอาจไม่โหลดล่วงหน้า บางทีทัศนวิสัย: การซ่อนจะใช้งานได้ดีกว่า แต่ฉันไม่ได้ทดสอบสิ่งนี้ ขอบคุณ Marco Del Valle ที่ชี้เรื่องนี้


ขอบคุณ mplungjan แม้ว่ามันจะไม่ได้ช่วยฉันในกรณีนี้ แต่ก็เป็นเรื่องดีที่รู้
Francisc

5
ฉันพูดถูกหรือไม่ว่านี่จะทำให้การโหลดหน้าเว็บช้าลงเนื่องจากต้องดาวน์โหลดรูปภาพเหล่านี้ก่อนที่หน้าเว็บจะสามารถเริ่มกิจกรรมการโหลดได้
jakubiszon

3
ฉันไม่เชื่อว่าสิ่งเหล่านี้จะทำงานบนเบราว์เซอร์ทั้งหมด ฉันรู้ว่าในภาพ Chrome ไม่ได้โหลดจนกว่าจะสามารถมองเห็นได้ จะต้องลบการแสดงผล: ไม่มี; และลองวางและวางตำแหน่งเพื่อให้มองไม่เห็น สามารถซ่อนด้วย JS หลังจากทุกอย่างโหลดถ้าจำเป็น
Bullyen

3
ภาพพื้นหลังในองค์ประกอบที่มีdisplay: noneจะไม่โหลดล่วงหน้า
Marquizzo

1
วิธีนี้ใช้ได้กับฉันเท่านั้นฉัน (1) ไม่ได้ใช้display: none(2) ไม่ได้ใช้width/height: 0, (3) ใส่no-repeatและตำแหน่งที่ภาพนั้นจะอยู่ด้านนอก ( -<width>px -<height>pxจะพาคุณไปที่นั่นดังนั้นหากรูปภาพของคุณมีทั้งหมด 100px ความสูงหรือน้อยกว่าคุณสามารถใช้0 -100pxในคำอื่น ๆurl(...) no-repeat 0 -100px.
อเล็กซิส Wilke

11

ฉันแนะนำให้คุณใช้ลอง / จับเพื่อป้องกันปัญหาที่อาจเกิดขึ้น:

OOP:

    var preloadImage = function (url) {
        try {
            var _img = new Image();
            _img.src = url;
        } catch (e) { }
    }

มาตรฐาน:

    function preloadImage (url) {
        try {
            var _img = new Image();
            _img.src = url;
        } catch (e) { }
    }

นอกจากนี้ในขณะที่ฉันรัก DOM เบราว์เซอร์ที่โง่ ๆ อาจมีปัญหากับคุณที่ใช้ DOM ดังนั้นควรหลีกเลี่ยงโดยสิ้นเชิง IMHO ตรงกันข้ามกับการสนับสนุนของ freedev รูปภาพ () มีการสนับสนุนที่ดีขึ้นในเบราว์เซอร์ถังขยะเก่า


7
ฉันไม่คิดว่านี่เป็นวิธีที่คุณจับข้อผิดพลาดเมื่อโหลดรูปภาพใน javascript - มีบางอย่างที่เหมือน _img.onerror ที่สามารถใช้ (ควร?)
Greg0ry

3
"ปัญหาที่เป็นไปได้" คืออะไร
Kissaki

ปัญหาเช่นการสนับสนุนสำหรับคำสั่ง ลองใช้ไซต์ "สามารถใช้" เพื่อดูว่าเบราว์เซอร์ที่คุณต้องการให้การสนับสนุนนั้นรองรับคำสั่ง javascript หรือไม่
เดฟ

10

วิธีนี้ค่อนข้างซับซ้อนกว่านี้เล็กน้อย ที่นี่คุณเก็บภาพที่โหลดไว้ล่วงหน้าทั้งหมดในคอนเทนเนอร์อาจเป็น div และหลังจากที่คุณสามารถแสดงภาพหรือย้ายภายใน DOM ไปยังตำแหน่งที่ถูกต้อง

function preloadImg(containerId, imgUrl, imageId) {
    var i = document.createElement('img'); // or new Image()
    i.id = imageId;
    i.onload = function() {
         var container = document.getElementById(containerId);
         container.appendChild(this);
    };
    i.src = imgUrl;
}

ลองที่นี่ฉันได้เพิ่มความคิดเห็นไว้เล็กน้อย


7
const preloadImage = src => 
  new Promise(r => {
    const image = new Image()
    image.onload = r
    image.onerror = r
    image.src = src
  })


// Preload an image
await preloadImage('https://picsum.photos/100/100')

// Preload a bunch of images in parallel 
await Promise.all(images.map(x => preloadImage(x.src)))

5

ใช่มันใช้งานได้แต่เบราว์เซอร์จะถูกจำกัด (ระหว่าง 4-8) การโทรจริงดังนั้นจึงไม่แคช / โหลดภาพที่ต้องการทั้งหมดล่วงหน้า

วิธีที่ดีกว่าในการทำเช่นนี้คือการเรียกใช้ onload ก่อนที่จะใช้ภาพเช่น:

function (imageUrls, index) {  
    var img = new Image();

    img.onload = function () {
        console.log('isCached: ' + isCached(imageUrls[index]));
        *DoSomething..*

    img.src = imageUrls[index]
}

function isCached(imgUrl) {
    var img = new Image();
    img.src = imgUrl;
    return img.complete || (img .width + img .height) > 0;
}

คุณช่วยลิงค์อ้างอิงบางส่วนเกี่ยวกับเบราว์เซอร์นี้ได้ไหม?
nulll

เป็นการยากที่จะหาข้อมูลอ้างอิง (อย่างน้อยฉันไม่เคยพบเจอ) แต่ฉันใช้โครงการของตัวเองเพื่อทดสอบสิ่งนี้ในขณะที่มองสิ่งที่ถูกโหลดจากแคชและการโทรเซิร์ฟเวอร์โดยใช้แท็บเครือข่ายของ Chrome ในเครื่องมือสำหรับนักพัฒนา Chrome (f12) ฉันพบพฤติกรรมเดียวกันใน Firefox และใช้อุปกรณ์พกพา
Robin

1
-1 เนื่องจากฉันทดสอบการอ้างสิทธิ์อย่างรอบคอบในประโยคแรกที่นี่และพบว่าเป็นเท็จอย่างน้อยในเบราว์เซอร์สมัยใหม่ ดู stackoverflow.com/a/56012084/1709587 ใช่มีข้อ จำกัด เกี่ยวกับจำนวนสูงสุดของการร้องขอ HTTP แบบขนานที่เบราว์เซอร์เต็มใจที่จะส่ง แต่ในที่สุดก็สามารถขอให้ทุก URL ที่คุณเรียก preload ได้ในที่สุดแม้ว่าคุณจะทำตามที่แสดงในคำถามก็ตาม
Mark Amery

5

นี่คือแนวทางของฉัน:

var preloadImages = function (srcs, imgs, callback) {
    var img;
    var remaining = srcs.length;
    for (var i = 0; i < srcs.length; i++) {
        img = new Image;
        img.onload = function () {
            --remaining;
            if (remaining <= 0) {
                callback();
            }
        };
        img.src = srcs[i];
        imgs.push(img);
    }
};

4

โซลูชั่นสำหรับเบราว์เซอร์ที่ใช้กับ ECMAScript 2017

หมายเหตุ: สิ่งนี้จะใช้ได้หากคุณใช้ transpiler เช่น Babel

'use strict';

function imageLoaded(src, alt = '') {
    return new Promise(function(resolve) {
        const image = document.createElement('img');

        image.setAttribute('alt', alt);
        image.setAttribute('src', src);

        image.addEventListener('load', function() {
            resolve(image);
        });
    });
}

async function runExample() {
    console.log("Fetching my cat's image...");

    const myCat = await imageLoaded('https://placekitten.com/500');

    console.log("My cat's image is ready! Now is the time to load my dog's image...");

    const myDog = await imageLoaded('https://placedog.net/500');

    console.log('Whoa! This is now the time to enable my galery.');

    document.body.appendChild(myCat);
    document.body.appendChild(myDog);
}

runExample();

คุณอาจรอโหลดรูปภาพทั้งหมด

async function runExample() {
    const [myCat, myDog] = [
        await imageLoaded('https://placekitten.com/500'),
        await imageLoaded('https://placedog.net/500')
    ];

    document.body.appendChild(myCat);
    document.body.appendChild(myDog);
}

หรือใช้Promise.allเพื่อโหลดมันแบบขนาน

async function runExample() {
    const [myCat, myDog] = await Promise.all([
        imageLoaded('https://placekitten.com/500'),
        imageLoaded('https://placedog.net/500')
    ]);

    document.body.appendChild(myCat);
    document.body.appendChild(myDog);
}

สัญญาเพิ่มเติมเกี่ยวกับ

เพิ่มเติมเกี่ยวกับ "Async" ฟังก์ชั่น

เพิ่มเติมเกี่ยวกับการกำหนด destructuring

เพิ่มเติมเกี่ยวกับ ECMAScript 2015

เพิ่มเติมเกี่ยวกับ ECMAScript 2017


2

ฉันสามารถยืนยันได้ว่าวิธีการในคำถามนั้นเพียงพอที่จะทริกเกอร์รูปภาพให้ดาวน์โหลดและแคช (เว้นแต่คุณจะห้ามไม่ให้เบราว์เซอร์ทำเช่นนั้นผ่านทางส่วนหัวการตอบกลับของคุณ) อย่างน้อย:

  • Chrome 74
  • Safari 12
  • Firefox 66
  • ขอบ 17

ในการทดสอบนี้ฉันได้สร้างเว็บแอปเล็ก ๆ ที่มีจุดปลายหลายจุดที่แต่ละสลีปเป็นเวลา 10 วินาทีก่อนที่จะแสดงรูปลูกแมว จากนั้นฉันก็เพิ่มหน้าเว็บสองหน้าซึ่งหนึ่งในนั้นมี<script>แท็กที่ลูกแมวแต่ละตัวถูกโหลดไว้ล่วงหน้าโดยใช้preloadImageฟังก์ชั่นจากคำถามและอีกส่วนหนึ่งรวมถึงลูกแมวทั้งหมดที่ใช้<img>แท็กด้วย

ในเบราว์เซอร์ทั้งหมดข้างต้นฉันพบว่าถ้าฉันไปที่หน้าตัวโหลดล่วงหน้าก่อนรอสักครู่แล้วไปที่หน้าด้วย<img>แท็กลูกแมวของฉันจะแสดงผลทันที นี่แสดงให้เห็นว่า preloader โหลดลูกแมวเข้าแคชในเบราว์เซอร์ทั้งหมดที่ทดสอบสำเร็จ

คุณสามารถมองเห็นหรือลองใช้แอพลิเคชันที่ผมใช้ในการทดสอบนี้ที่https://github.com/ExplodingCabbage/preloadImage-test

หมายเหตุโดยเฉพาะอย่างยิ่งที่ใช้เทคนิคนี้ในการทำงานของเบราว์เซอร์ดังกล่าวข้างต้นแม้ว่าจำนวนของภาพที่ถูกคล้องกว่าเกินกว่าจำนวนของการร้องขอขนานที่เบราว์เซอร์ก็เต็มใจที่จะทำในช่วงเวลาที่ตรงกันข้ามกับสิ่งที่เป็นคำตอบของโรบินแสดงให้เห็น อัตราที่ภาพของคุณพรีโหลดของหลักสูตรจะถูก จำกัด ด้วยวิธีการหลายขนานขอเบราว์เซอร์ยินดีที่จะส่ง แต่มันจะในที่สุดก็ขอ URL แต่ละภาพที่คุณโทรpreloadImage()ได้ที่


เป็นเรื่องสำคัญหรือไม่ว่ารูปภาพจะถูกแคชในหน่วยความจำหรือดิสก์? ดูเหมือนว่าวิธี OP จะแคชบนดิสก์ซึ่งตรงข้ามกับวิธี @clintgh ซึ่งได้รับการแคชในหน่วยความจำ
Chris L

1

คุณสามารถย้ายรหัสนี้ไปที่ index.html เพื่อโหลดภาพล่วงหน้าจาก URL ใดก็ได้

<link rel="preload" href="https://via.placeholder.com/160" as="image">

0

เบราว์เซอร์จะทำงานได้ดีที่สุดโดยใช้แท็กลิงก์ในส่วนหัว

export function preloadImages (imageSources: string[]): void {
  imageSources
    .forEach(i => {
      const linkEl = document.createElement('link');
      linkEl.setAttribute('rel', 'preload');
      linkEl.setAttribute('href', i);
      linkEl.setAttribute('as', 'image');
      document.head.appendChild(linkEl);
    });
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.