Tải trước hình ảnh với jQuery


689

Tôi đang tìm kiếm một cách nhanh chóng và dễ dàng để tải trước hình ảnh bằng JavaScript. Tôi đang sử dụng jQuery nếu điều đó quan trọng.

Tôi đã thấy điều này ở đây ( http: //nettuts.com ... ):

function complexLoad(config, fileNames) {
  for (var x = 0; x < fileNames.length; x++) {
    $("<img>").attr({
      id: fileNames[x],
      src: config.imgDir + fileNames[x] + config.imgFormat,
      title: "The " + fileNames[x] + " nebula"
    }).appendTo("#" + config.imgContainer).css({ display: "none" });
  }
};

Nhưng, nó có vẻ hơi quá so với những gì tôi muốn!

Tôi biết có các plugin jQuery ngoài đó làm điều này nhưng tất cả chúng có vẻ hơi lớn (về kích thước); Tôi chỉ cần một cách nhanh chóng, dễ dàng và ngắn gọn để tải trước hình ảnh!


10
$.each(arguments,function(){(new Image).src=this});
David Hellsing

Câu trả lời:


969

Nhanh chóng dễ dàng:

function preload(arrayOfImages) {
    $(arrayOfImages).each(function(){
        $('<img/>')[0].src = this;
        // Alternatively you could use:
        // (new Image()).src = this;
    });
}

// Usage:

preload([
    'img/imageName.jpg',
    'img/anotherOne.jpg',
    'img/blahblahblah.jpg'
]);

Hoặc, nếu bạn muốn một plugin jQuery:

$.fn.preload = function() {
    this.each(function(){
        $('<img/>')[0].src = this;
    });
}

// Usage:

$(['img1.jpg','img2.jpg','img3.jpg']).preload();

25
Không phải phần tử hình ảnh cần được chèn vào DOM để đảm bảo trình duyệt lưu trữ nó?
JoshNaro

8
Tôi tin rằng $('<img />')chỉ cần tạo một yếu tố hình ảnh trong bộ nhớ (xem liên kết ). Có vẻ như '$('<img src="' + this + '" />')thực sự sẽ tạo ra phần tử trong một DIV ẩn, bởi vì nó "phức tạp" hơn. Tuy nhiên, tôi không nghĩ rằng điều này là cần thiết cho hầu hết các trình duyệt.
JoshNaro

104
Đó là một cách kỳ lạ để viết một plugin jQuery. Tại sao không $.preload(['img1.jpg', 'img2.jpg'])?
alex

12
Hãy chắc chắn gọi cái này bên trong $(window).load(function(){/*preload here*/});bởi vì cách đó tất cả các hình ảnh trong tài liệu được tải trước tiên, có khả năng là chúng cần thiết trước tiên.
Jasper Kennis

12
@RegionalC - Có thể an toàn hơn một chút khi đặt loadsự kiện trước khi đặt srcchỉ trong trường hợp hình ảnh kết thúc tải trước khi sự kiện tải được đặt? $('<img/>').load(function() { /* it's loaded! */ }).attr('src', this);
sparebytes

102

Đây là phiên bản được điều chỉnh của phản hồi đầu tiên thực sự tải hình ảnh vào DOM và ẩn nó theo mặc định.

function preload(arrayOfImages) {
    $(arrayOfImages).each(function () {
        $('<img />').attr('src',this).appendTo('body').css('display','none');
    });
}

38
hide()là ngắn gọn hơn css('display', 'none').
alex

6
Lợi thế của việc chèn chúng vào DOM là gì?
alex

Tôi cũng muốn biết lợi thế của việc chèn chúng vào DOM.
jedmao

11
Từ kinh nghiệm của tôi, việc tải trước một hình ảnh vào DOM làm cho trình duyệt nhận ra sự tồn tại của nó và để nó được lưu trữ đúng cách. Mặt khác, hình ảnh chỉ tồn tại trong bộ nhớ chỉ hoạt động cho các ứng dụng trang đơn.
Dennis Rongo

Dennis Rongo. Việc thêm hình ảnh vào DOM cố định tải lại ngẫu nhiên trên Chrome. Cảm ơn!
tmorell

52

Sử dụng đối tượng hình ảnh JavaScript .

Chức năng này cho phép bạn kích hoạt một cuộc gọi lại khi tải tất cả các hình ảnh. Tuy nhiên, lưu ý rằng nó sẽ không bao giờ kích hoạt cuộc gọi lại nếu ít nhất một tài nguyên không được tải. Điều này có thể dễ dàng khắc phục bằng cách thực hiện onerrorgọi lại và tăng loadedgiá trị hoặc xử lý lỗi.

var preloadPictures = function(pictureUrls, callback) {
    var i,
        j,
        loaded = 0;

    for (i = 0, j = pictureUrls.length; i < j; i++) {
        (function (img, src) {
            img.onload = function () {                               
                if (++loaded == pictureUrls.length && callback) {
                    callback();
                }
            };

            // Use the following callback methods to debug
            // in case of an unexpected behavior.
            img.onerror = function () {};
            img.onabort = function () {};

            img.src = src;
        } (new Image(), pictureUrls[i]));
    }
};

preloadPictures(['http://foo/picture.bar', 'http://foo/picture.bar', 'http://foo/picture.bar', 'http://foo/picture.bar'], function(){
    console.log('a');
});

preloadPictures(['http://foo/picture.bar', 'http://foo/picture.bar', 'http://foo/picture.bar', 'http://foo/picture.bar'], function(){
    console.log('b');
});

4
Làm đúng, sử dụng đối tượng JavaScript Image. Bạn có quan sát mọi người làm sai trong những câu trả lời này không?
alex

Mã rực rỡ. Tôi có đúng không khi nghĩ rằng điều này sẽ kích hoạtonload sự kiện ngay cả khi hình ảnh được lưu trữ? (Vì img.onloadđược khai báo trước). Đây là những gì thử nghiệm của tôi cho thấy.
Bắt đầu

3
@alex có khả năng, vâng. Nếu mục tiêu là tải trước (gợi ý thứ tự hiệu năng) thì tôi muốn xem tùy chọn JS thô thay vì tùy chọn phụ thuộc jQuery.
Charlie Schliesser

Tôi thích giải pháp này nhưng tôi mới phát hiện ra rằng nó không hoạt động trong Firefox của tôi. Có phải chỉ tôi hoặc những người khác có cùng một vấn đề?
gazillion

@Gazillion cho biết thêm chi tiết. Làm thế nào để bạn xác định "không làm việc"? Phiên bản FF là gì?
Gajus

35

JP, Sau khi kiểm tra giải pháp của bạn, tôi vẫn gặp sự cố trong Firefox khi nó không tải trước hình ảnh trước khi di chuyển cùng với việc tải trang. Tôi phát hiện ra điều này bằng cách đặt một số sleep(5)trong kịch bản phía máy chủ của tôi. Tôi đã thực hiện các giải pháp sau đây dựa trên giải pháp của bạn.

Về cơ bản tôi đã thêm một cuộc gọi lại vào plugin tải trước jQuery của bạn, để nó được gọi sau khi tất cả các hình ảnh được tải đúng cách.

// Helper function, used below.
// Usage: ['img1.jpg','img2.jpg'].remove('img1.jpg');
Array.prototype.remove = function(element) {
  for (var i = 0; i < this.length; i++) {
    if (this[i] == element) { this.splice(i,1); }
  }
};

// Usage: $(['img1.jpg','img2.jpg']).preloadImages(function(){ ... });
// Callback function gets called after all images are preloaded
$.fn.preloadImages = function(callback) {
  checklist = this.toArray();
  this.each(function() {
    $('<img>').attr({ src: this }).load(function() {
      checklist.remove($(this).attr('src'));
      if (checklist.length == 0) { callback(); }
    });
  });
};

Không quan tâm, trong ngữ cảnh của tôi, tôi đang sử dụng điều này như sau:

$.post('/submit_stuff', { id: 123 }, function(response) {
  $([response.imgsrc1, response.imgsrc2]).preloadImages(function(){
    // Update page with response data
  });
});

Hy vọng rằng điều này sẽ giúp ai đó đến trang này từ Google (như tôi đã làm) đang tìm kiếm một giải pháp để tải trước hình ảnh trên các cuộc gọi Ajax.


2
Thay vào đó, đối với những người không quan tâm đến việc làm rối Array.prototype, bạn có thể làm: checklist = this.lengthVà trong hàm onload: checklist--Sau đó:if (checklist == 0) callback();
MiniGod

3
Mọi thứ sẽ ổn tuy nhiên việc thêm mới hoặc xóa các phương thức hiện có vào một đối tượng bạn không sở hữu là một trong những thực tiễn tồi tệ nhất trong JavaScript.
Rihards

Đẹp và nhanh chóng, nhưng mã này sẽ không bao giờ thực hiện cuộc gọi lại nếu một trong những hình ảnh bị hỏng hoặc không thể tải.
SammyK

.attr({ src: this }).load(function() {...})Nên .load(function() {...}).attr({ src: this })nếu không bạn sẽ có vấn đề bộ nhớ đệm.
Kevin B

25

Mã jQuery một dòng này tạo (và tải) một phần tử DOM img mà không hiển thị nó:

$('<img src="img/1.jpg"/>');

4
@huzzah - Bạn có thể tốt hơn nếu chỉ sử dụng các họa tiết. Yêu cầu http ít hơn. :)
Alex K

22
$.fn.preload = function (callback) {
  var length = this.length;
  var iterator = 0;

  return this.each(function () {
    var self = this;
    var tmp = new Image();

    if (callback) tmp.onload = function () {
      callback.call(self, 100 * ++iterator / length, iterator === length);
    };

    tmp.src = this.src;
  });
};

Cách sử dụng khá đơn giản:

$('img').preload(function(perc, done) {
  console.log(this, perc, done);
});

http://jsfiddle.net/yckart/ACbTK/


Đây là một câu trả lời thực sự tốt, nó thực sự nên là câu trả lời bình chọn hàng đầu imho.
ngủ

1
Một số từ giải thích sẽ là tốt đẹp.
robsch

Câu trả lời hay, nhưng tôi khuyên bạn nên sử dụng picsum.photos thay vì lorempixel.com
Aleksandar

19

Tôi có một plugin nhỏ xử lý việc này.

Nó được gọi là WaitForImages và nó có thể xử lý imgcác phần tử hoặc bất kỳ phần tử nào có tham chiếu đến hình ảnh trong CSS, ví dụ:div { background: url(img.png) } .

Nếu bạn chỉ đơn giản muốn tải tất cả các hình ảnh, bao gồm cả những hình ảnh được tham chiếu trong CSS, thì đây là cách bạn sẽ làm điều đó :)

$('body').waitForImages({
    waitForAll: true,
    finished: function() {
       // All images have loaded.
    }  
});

Có phải plugin này khiến hình ảnh chưa xuất hiện trên trang chưa được tải hoặc chỉ đính kèm các sự kiện vào hình ảnh đã được tải?
Dave Cameron

@DaveCameron Không tôn trọng nếu hình ảnh có thể nhìn thấy hay không. Bạn có thể dễ dàng rẽ nhánh nó và thực hiện thay đổi đó - chỉ cần thêm :visiblevào bộ chọn tùy chỉnh.
alex

Plugin này trông rất thú vị. Tuy nhiên, tài liệu jquery nhấn mạnh rằng loadsự kiện này, khi được áp dụng cho hình ảnh: "không hoạt động ổn định và không đáng tin cậy trên nhiều trình duyệt". Làm thế nào các plugin quản lý để có được xung quanh đó?
EleventyOne

@EleventyOne Kiểm tra nguồn liên quan đến bộ chọn tùy chỉnh.
alex

@alex Làm thế nào để plugin này tải hình ảnh được thiết lập trong css on: hover của một phần tử? Nó không hoạt động đối với tôi
Philipp Hofmann

13

bạn có thể tải hình ảnh trong html của mình ở đâu đó bằng css display:none; quy tắc , sau đó hiển thị chúng khi bạn muốn với js hoặc jquery

không sử dụng các hàm js hoặc jquery để tải trước chỉ là quy tắc css Vs nhiều dòng js sẽ được thực thi

ví dụ: Html

<img src="someimg.png" class="hide" alt=""/>

Css:

.hide{
display:none;
}

jQuery:

//if want to show img 
$('.hide').show();
//if want to hide
$('.hide').hide();

Tải trước hình ảnh bằng jquery / javascript không tốt vì hình ảnh mất vài mili giây để tải trang + bạn có một phần nghìn giây để tập lệnh được phân tích cú pháp và thực thi, đặc biệt sau đó nếu chúng là hình ảnh lớn, thì việc ẩn chúng trong hml cũng tốt hơn cho hiệu suất, vì hình ảnh thực sự được tải sẵn mà không hiển thị, cho đến khi bạn hiển thị điều đó!


2
Thông tin thêm về phương pháp này có thể được tìm thấy ở đây: perishablepress.com/...
gdelfino

3
Nhưng bạn cần lưu ý rằng kỹ thuật này có một nhược điểm lớn: trang của bạn sẽ không được tải hoàn toàn cho đến khi tất cả các hình ảnh được tải. Tùy thuộc vào số lượng hình ảnh để tải trước và kích thước của chúng, việc này có thể mất một chút thời gian. Thậm chí tệ hơn, nếu thẻ <img> không chỉ định chiều cao và chiều rộng, một số trình duyệt có thể đợi cho đến khi hình ảnh được tìm nạp trước khi hiển thị phần còn lại của trang.

@ Alex bạn anyway cần phải nạp img, bạn được tự do để lựa chọn nếu để tải chúng với html và tránh bất kỳ loại nhấp nháy và một nửa nạp hình ảnh, hoặc nếu bạn muốn để có được tốc độ nhanh hơn sẽ cho một giải pháp không ổn định
itsme

cũng tôi thực sự nghĩ rằng việc tạo ra các thẻ html với javascript không thể đọc được ở tất cả các chỉ 2 cent của tôi
itsme

1
Tôi cần một giải pháp rất nhanh cho một dự án rất phức tạp và đây là nó.
Bão tố

13

jquery này imageLoaderplugin này chỉ là 1,39kb

sử dụng:

$({}).imageLoader({
    images: [src1,src2,src3...],
    allcomplete:function(e,ui){
        //images are ready here
        //your code - site.fadeIn() or something like that
    }
});

Ngoài ra còn có các tùy chọn khác như bạn muốn tải hình ảnh đồng bộ hay không đồng bộ và một sự kiện hoàn chỉnh cho từng hình ảnh riêng lẻ.


@AamirAfridi Tại sao vậy?
Ian

1
@Ian vì tài liệu đã sẵn sàng vào thời điểm cuộc gọi lại được kích hoạt. $ (tài liệu). yet được sử dụng để đảm bảo DOM của bạn được tải. Khi nói đến cuộc gọi lại, điều đó có nghĩa là tất cả các hình ảnh được tải có nghĩa là DOM yoru đã được tải nên không cần tài liệu. Đã có trong một cuộc gọi lại.
Aamir Afridi

1
@AamirAfridi Không có gì đảm bảo rằng tài liệu đã sẵn sàng trong cuộc gọi lại ... bạn đang suy luận điều đó từ đâu? Không có gì trên trang của plugin nói rằng cuộc allcompletegọi lại được thực thi sau khi DOM sẵn sàng. Có một cơ hội khá tốt là các hình ảnh kết thúc tải sau khi DOM đã sẵn sàng, nhưng không có lý do gì để giả sử. Tôi không biết tại sao bạn nghĩ các cuộc gọi lại là kỳ diệu và được thực thi sau khi DOM sẵn sàng ... bạn có thể giải thích bạn đang nhận được điều đó từ đâu không? Chỉ vì các hình ảnh được tải không có nghĩa là DOM đã sẵn sàng
Ian

@AamirAfridi Và tài liệu cho plugin thậm chí còn nói: NB to use this as an image preloader simply put your $(document).ready(function(){}); into your 'allcomplete' event : > simples!... nó trực tiếp đề xuất những gì câu trả lời này cho thấy.
Ian

5
@AamirAfridi Điều đó không có ý nghĩa cho tình huống này. Các plugin là một trình tải trước . Bạn muốn thực hiện nó càng sớm càng tốt. Và có rất nhiều điều mà không phụ thuộc vào DOM mà bạn muốn lửa càng sớm càng tốt, và sau đó khi DOM đã sẵn sàng, làm điều gì đó.
Ian

9

Cách nhanh chóng, không có plugin để tải trước hình ảnh trong jQuery và có chức năng gọi lại là tạo nhiều imgthẻ cùng một lúc và đếm các phản hồi, ví dụ:

function preload(files, cb) {
    var len = files.length;
    $(files.map(function(f) {
        return '<img src="'+f+'" />';
    }).join('')).load(function () {
        if(--len===0) {
            cb();
        }
    });
}

preload(["one.jpg", "two.png", "three.png"], function() {
    /* Code here is called once all files are loaded. */
});
    

Lưu ý rằng nếu bạn muốn hỗ trợ IE7, bạn sẽ cần sử dụng phiên bản nhỏ hơn một chút này (cũng hoạt động trong các trình duyệt khác):

function preload(files, cb) {
    var len = files.length;
    $($.map(files, function(f) {
        return '<img src="'+f+'" />';
    }).join('')).load(function () {
        if(--len===0) {
            cb();
        }
    });
}

7

Cám ơn vì cái này! Tôi muốn nói thêm một chút về câu trả lời của JP - Tôi không biết điều này có giúp được ai không, nhưng bằng cách này bạn không phải tạo ra một mảng hình ảnh và bạn có thể tải trước tất cả các hình ảnh lớn của mình nếu bạn đặt tên chính xác cho ngón tay cái của bạn Điều này rất hữu ích vì tôi có ai đó đang viết tất cả các trang bằng html và nó đảm bảo một bước nữa để họ thực hiện - loại bỏ nhu cầu tạo mảng hình ảnh và một bước khác để mọi thứ có thể được giải quyết.

$("img").each(function(){
    var imgsrc = $(this).attr('src');
    if(imgsrc.match('_th.jpg') || imgsrc.match('_home.jpg')){
      imgsrc = thumbToLarge(imgsrc);
      (new Image()).src = imgsrc;   
    }
});

Về cơ bản, đối với mỗi hình ảnh trên trang, nó lấy src của mỗi hình ảnh, nếu nó phù hợp với tiêu chí nhất định (là ngón tay cái hoặc hình ảnh trang chủ), nó sẽ thay đổi tên (một chuỗi cơ bản thay thế trong src hình ảnh), sau đó tải hình ảnh .

Trong trường hợp của tôi, trang có đầy đủ các hình ảnh ngón tay cái được đặt tên giống như image_th.jpg và tất cả các hình ảnh lớn tương ứng được đặt tên là image_lg.jpg. Ngón cái to lớn chỉ thay thế _th.jpg bằng _lg.jpg và sau đó tải trước tất cả các hình ảnh lớn.

Hy vọng điều này sẽ giúp được ai đó.


3
    jQuery.preloadImage=function(src,onSuccess,onError)
    {
        var img = new Image()
        img.src=src;
        var error=false;
        img.onerror=function(){
            error=true;
            if(onError)onError.call(img);
        }
        if(error==false)    
        setTimeout(function(){
            if(img.height>0&&img.width>0){ 
                if(onSuccess)onSuccess.call(img);
                return img;
            }   else {
                setTimeout(arguments.callee,5);
            }   
        },0);
        return img; 
    }

    jQuery.preloadImages=function(arrayOfImages){
        jQuery.each(arrayOfImages,function(){
            jQuery.preloadImage(this);
        })
    }
 // example   
    jQuery.preloadImage(
        'img/someimage.jpg',
        function(){
            /*complete
            this.width!=0 == true
            */
        },
        function(){
            /*error*/
        }
    )

7
Chào mừng bạn đến với Stack Overflow! Thay vì chỉ đăng một khối mã, vui lòng giải thích lý do tại sao mã này giải quyết vấn đề được đặt ra. Không có lời giải thích, đây không phải là một câu trả lời.
Martijn Pieters

3

Tôi sử dụng mã sau đây:

$("#myImage").attr("src","img/spinner.gif");

var img = new Image();
$(img).load(function() {
    $("#myImage").attr("src",img.src);
});
img.src = "http://example.com/imageToPreload.jpg";

Liên kết với sự kiện tải trước, sau đó đặt src.
Kevin B

1

Tôi sẽ sử dụng tệp Manifest để thông báo cho các trình duyệt web (hiện đại) để tải tất cả các hình ảnh có liên quan và lưu trữ chúng. Sử dụng Grunt và grunt-manifest để thực hiện việc này một cách tự động và không bao giờ lo lắng về các tập lệnh tải trước, bộ vô hiệu hóa bộ đệm, CDN, v.v.

https://github.com/gunta/grunt-manifest


0

Điều này làm việc cho tôi ngay cả trong IE9:

$('<img src="' + imgURL + '"/>').on('load', function(){ doOnLoadStuff(); });

1
Điều này cuối cùng sẽ thất bại do bộ nhớ đệm, luôn liên kết sự kiện tải trước khi đặt thuộc tính src.
Kevin B

0

Tôi muốn làm điều này với lớp phủ tùy chỉnh API Google Maps. Mã mẫu của họ chỉ đơn giản là sử dụng JS để chèn các phần tử IMG và hộp giữ chỗ hình ảnh được hiển thị cho đến khi hình ảnh được tải. Tôi tìm thấy một câu trả lời ở đây có hiệu quả với tôi: https://stackoverflow.com/a/10863680/2095698 .

$('<img src="'+ imgPaht +'">').load(function() {
$(this).width(some).height(some).appendTo('#some_target');
});

Điều này tải trước một hình ảnh như được đề xuất trước đó, và sau đó sử dụng trình xử lý để nối thêm đối tượng img sau khi URL img được tải. Tài liệu của jQuery cảnh báo rằng các hình ảnh được lưu trong bộ nhớ cache không hoạt động tốt với mã xử lý sự kiện / xử lý này, nhưng nó hoạt động với tôi trong FireFox và Chrome và tôi không phải lo lắng về IE.


Điều này sẽ không hoạt động tốt với hình ảnh được lưu trong bộ nhớ cache như bạn đã tìm ra. cách giải quyết là liên kết sự kiện tải trước, sau đó đặt thuộc tính src.
Kevin B

-1
function preload(imgs) {
    $(imgs).each(function(index, value) {
        $('<img />').attr('src', value).appendTo('body').css('display', 'none');
    });
}

.attr('src',value) không phải .attr('src',this)

chỉ để chỉ ra thôi :)


Phạm vi thisbên trong cuộc gọi lại được chuyển đến $.eachđược gán cho giá trị được lặp lại.
Oybek

? $ (['img1.jpg', 'img2.jpg', 'img3.jpg']). mỗi (hàm (chỉ mục, giá trị) {console.log (value); // img1.jpg console.log (này) ; // Chuỗi {0 = "i", 1 = "m", 2 = "g", thêm ...} $ ('<img />'). Attr ('src', this) .appendTo (' cơ thể '). css (' hiển thị ',' không ');});
Người đánh cá

Hừm. Tôi đoán bạn ở ngay đây. Ví dụ $("div").each(function(i, el){ console.log(el == this);});tạo tất cả trues; Lặp đi lặp lại trên mảng dường như hành xử khác nhau.
Oybek

-2

5 dòng trong cà phê

array = ['/img/movie1.png','/img/movie2.png','/img/movie3.png']

$(document).ready ->
  for index, image of array
    img[index] = new Image()
    img[index].src = image

2
Bạn có thể mở rộng về cách giải pháp hoạt động để giải quyết câu hỏi trong OP không? Và có thể nhận xét mã của bạn để người khác có thể dễ dàng hiểu nó hơn?
Ro Yo Mi

-4

Đối với những người biết một chút về bản mô tả, bạn có thể kiểm tra trình phát flash, với nỗ lực tối thiểu và tạo trình tải trước flash, bạn cũng có thể xuất sang html5 / Javascript / Jquery. Để sử dụng nếu trình phát flash không được phát hiện, hãy kiểm tra các ví dụ về cách thực hiện việc này với vai trò youtube trở lại trình phát html5 :) Và tạo riêng của bạn. Tôi không có thông tin chi tiết, vì tôi chưa bắt đầu, nếu tôi không quên, tôi sẽ đăng nó sau và sẽ thử một số mã Jquery độc lập để khai thác.


Đây không phải là một giải pháp. Trình duyệt không hỗ trợ Flash player theo mặc định. Có thể dễ dàng đạt được với JavaScript là ngôn ngữ trình duyệt gốc.
Usman Ahmed

Tôi nhớ những người ghét Flash từ năm 2012 ... không bình thường ... ở mọi nơi tôi đã bị tấn công khi tôi thậm chí sử dụng từ Flash.
Peter Gruppelaar
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.