Sử dụng Backbone.js để tăng tốc độ tương tác

Tác Giả: Monica Porter
Ngày Sáng TạO: 13 Hành Khúc 2021
CậP NhậT Ngày Tháng: 15 Có Thể 2024
Anonim
Dashbit Unity Tutorial #4: Memory, optimize tricks
Băng Hình: Dashbit Unity Tutorial #4: Memory, optimize tricks

NộI Dung

Nếu bạn đang muốn nhanh chóng xây dựng một công cụ JavaScript nhỏ, có thể bạn đang không nghĩ đến việc sử dụng một khuôn khổ. Dễ dàng hơn để hack cùng một số mã jQuery hơn là cài đặt và tìm hiểu một khuôn khổ mới, phải không? Sai, Backbone.js là một khung công tác keo siêu nhẹ trông giống như JavaScript cũ thông thường mà bạn thường viết.

Chúng tôi thực hiện rất nhiều nguyên mẫu tĩnh ở đây tại ZURB, bởi vì chúng tôi muốn có thể nhấp qua các trang mà không cần phải viết bất kỳ mã phụ trợ nào. Thông thường, chúng tôi sẽ thả vào các hình ảnh giữ chỗ màu xám buồn tẻ hoặc đôi khi chúng tôi tìm kiếm trên Flickr các hình ảnh mẫu để giúp chúng tôi hình dung những gì có thể xảy ra trong bản nháp cuối cùng. Đó là cho đến một ngày thứ sáu kỳ diệu, khi chúng tôi quyết định sẽ thật tuyệt nếu viết một số JavaScript để giải quyết vấn đề của chúng tôi. Chúng tôi muốn có thể tìm kiếm và chọn ảnh trên Flickr, trực tiếp từ chính các ảnh giữ chỗ. Chúng tôi sẽ gọi nó là FlickrBomb, và đây là câu chuyện về cách chúng tôi xây dựng nó bằng cách sử dụng Backbone.js.


Chúng tôi thực sự khuyên bạn nên xem nhanh FlickrBomb trước khi đọc. Đó là một trong những loại giao dịch “một cú nhấp chuột có giá trị bằng một nghìn từ”. Hãy tiếp tục, chúng tôi sẽ đợi.

Ngày nay, có rất nhiều khung JavaScript trên khối, SproutCore, JavaScriptMVC, Spine, Sammy, Knockout. Nhưng chúng tôi thích Backbone.js cho dự án cụ thể này vì một vài lý do khác nhau:

1. Nó nhẹ (thực tế là 100% không có chất béo)

  • về trọng lượng, với phiên bản đóng gói mới nhất là khoảng 4,6kb
  • trong mã, chỉ hơn 1.000 dòng mã, không quá khó để lần theo dấu vết ngăn xếp vào bên trong mà không làm bạn mất trí

2. Nó trông giống như JavaScript

  • bởi vì nó là JavaScript, đó là nó và đó là tất cả
  • nó sử dụng jQuery, mà ngay cả bà của bạn ngày nay cũng biết

3. Sự bền bỉ siêu đơn giản


  • ra khỏi hộp, nó vẫn giữ dữ liệu vào một chương trình phụ trợ (thông qua REST), nhưng bằng cách thả vào một trình cắm duy nhất, nó sẽ lưu vào bộ nhớ cục bộ để thay thế
  • bởi vì nó trừu tượng hóa API tồn tại, chúng tôi có thể để nó tồn tại đối với phần phụ trợ REST bằng cách xóa plugin lưu trữ cục bộ

Hãy bắt đầu sau đó

Bởi vì Backbone.js chỉ là JavaScript, tất cả những gì chúng ta cần làm là đưa nó vào cùng với Underscore.js trên trang. jQuery không phải là một phụ thuộc cứng cho Backbone, nhưng chúng tôi sẽ sử dụng nó, vì vậy chúng tôi sẽ đưa nó vào đây. Chúng tôi cũng sẽ liên kết plugin bộ nhớ cục bộ, vì chúng tôi không muốn gặp rắc rối với việc thiết lập chương trình phụ trợ. Lưu ý rằng đã liên kết trực tiếp các tệp ở đây để đơn giản, nhưng bạn phải luôn lưu trữ nội dung của riêng mình trong quá trình sản xuất.

script src = "http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"> / script> script src = "http://documentcloud.github.com/backbone/ backbone-min.js "> / script> script src =" http://documentcloud.github.com/underscore/underscore-min.js "> / script> script src =" https://raw.github.com/ jeromegn / Backbone.localStorage / master / backbone.localStorage-min.js "> / script>

Tất cả mã sau trong bài viết này là dành riêng cho ứng dụng của chúng tôi, vì vậy chúng tôi có thể đưa nó vào tệp app.js hoặc chỉ nội tuyến nếu đó là thứ của bạn. Chỉ cần nhớ thêm nó sau Backbone. Backbone cho phép các phần trừu tượng của ứng dụng của chúng tôi, để biến chúng thành mô-đun để dễ dàng sử dụng lại và dễ đọc hơn đối với những người khác. Để minh họa rõ nhất sự trừu tượng đó, chúng tôi sẽ giải thích thiết kế của FlickrBomb từ dưới lên, bắt đầu với các mô hình và kết thúc bằng các khung nhìn.


Mô hình đầu tiên của chúng tôi

Nhiệm vụ đầu tiên sẽ giải quyết là kéo ảnh từ Flickr. Tạo mô hình FlickrImage trong xương sống là đủ đơn giản, chúng tôi sẽ tạo một mô hình mới có tên FlickrImage và thêm một số phương pháp để giúp chúng tôi có được kích thước ngón tay cái khác nhau.

var FlickrImage = Backbone.Model.extend ({fullsize_url: function () {return this.image_url ('medium');}, thumb_url: function () {return this.image_url ('square');}, image_url: function ( size) {var size_code; switch (size) {case 'square': size_code = '_s'; break; // 75x75 case 'medium': size_code = '_z'; break; // 640 trên trường hợp cạnh dài nhất 'Large ': size_code =' _b '; break; // 1024 ở cạnh dài nhất mặc định: size_code =' ';} return "http: // farm" + this.get (' farm ') + ".static.flickr.com / "+ this.get ('server') +" / "+ this.get ('id') +" _ "+ this.get ('secret') + size_code +" .webp ";}})

Mô hình trong Backbone là các đối tượng có thể tồn tại lâu dài và có một số chức năng được liên kết với chúng, giống như các mô hình trong các khuôn khổ MVC khác. Phần kỳ diệu của các mô hình Backbone là chúng ta có thể liên kết các sự kiện với các thuộc tính, để khi thuộc tính đó thay đổi, chúng ta có thể cập nhật các quan điểm của mình để phản ánh điều đó. Nhưng chúng tôi đang đi trước một chút.

Khi chúng tôi lấy ảnh từ Flickr, chúng tôi sẽ có đủ thông tin để tạo URL cho tất cả các kích thước. Tuy nhiên, assembly đó là do chúng tôi phụ thuộc, vì vậy chúng tôi đã triển khai hàm .image_url () nhận tham số kích thước và trả về một liên kết công khai. Vì đây là mô hình xương sống nên chúng ta có thể sử dụng this.get () để truy cập các thuộc tính trên mô hình. Vì vậy, với mô hình này, chúng ta có thể thực hiện như sau ở những nơi khác trong mã để lấy URL của hình ảnh Flickr.

flickrImage.image_url ('lớn')

Khá ngắn gọn phải không? Vì mô hình này dành riêng cho ứng dụng của chúng tôi, chúng tôi sẽ thêm một số chức năng trình bao bọc cho kích thước hình ảnh fullsize và thumb.

Một bộ sưu tập các hình ảnh

FlickrBomb xử lý các bộ sưu tập hình ảnh, không phải hình ảnh đơn lẻ và Backbone có một cách thuận tiện để lập mô hình này. Bộ sưu tập được đặt tên phù hợp là những gì chúng tôi sẽ sử dụng để nhóm các hình ảnh Flickr lại với nhau cho một trình giữ chỗ duy nhất.

var FlickrImages = Backbone.Collection.extend ({model: FlickrImage, key: flickrbombAPIkey, page: 1, fetch: function (từ khóa, thành công) {var self = this; success = thành công || $ .noop; this.keywords = từ khóa || this.keywords; $ .ajax ({url: 'http://api.flickr.com/services/rest/', data: {api_key: self.key, format: 'json', method: 'flickr. photos.search ', tags: this.keywords, per_page: 9, page: this.page, license: flickrbombLicenseTypes}, dataType:' jsonp ', jsonp:' jsoncallback ', success: function (response) {self.add (response .photos.photo); success ();}});}, nextPage: function (callback) {this.page + = 1; this.remove (this.models); this.fetch (null, callback);}, trước đây: function (callback) {if (this.page> 1) {this.page - = 1;} this.remove (this.models); this.fetch (null, callback);}});

Có một số điều cần lưu ý ở đây. Trước hết, mô hình thuộc tính cho bộ sưu tập biết loại mô hình mà nó đang thu thập. Chúng tôi cũng có một số thuộc tính mà chúng tôi đã khởi tạo để sử dụng sau này: khóa là khóa API Flickr của chúng tôi, bạn sẽ muốn thay thế flickrbombAPIkey bằng chuỗi khóa API Flickr của riêng mình. Nhận khóa API Flickr hoàn toàn miễn phí và dễ dàng, chỉ cần nhấp vào liên kết sau: www.flickr.com/services/api/misc.api_keys.html. Thuộc tính trang là trang ảnh Flickr hiện tại mà chúng tôi đang truy cập.

Phương thức lớn ở đây là .fetch (), nó tóm tắt các chi tiết của việc kéo ảnh từ API Flickr. Để tránh các vấn đề với các yêu cầu tên miền chéo, chúng tôi đang sử dụng JSONP, hỗ trợ cả API Flickr và jQuery. Các tham số khác mà chúng tôi đang chuyển tới API phải tự giải thích. Mối quan tâm đặc biệt là các hàm Backbone được gọi ở đây. Trong lần gọi lại thành công, chúng tôi đang sử dụng .add (), một hàm nhận một mảng các thuộc tính mô hình, tạo các cá thể mô hình từ các thuộc tính đó, sau đó thêm chúng vào bộ sưu tập.

Trước tiên, các hàm .nextPage () và .prevPage () thay đổi trang chúng tôi muốn hiển thị,
sử dụng hàm thu thập .remove (), để xóa tất cả các mô hình hiện có khỏi
bộ sưu tập và sau đó gọi tìm nạp để lấy ảnh cho trang hiện tại (mà chúng tôi chỉ
đã thay đổi).

FlickrBombImage

Làm việc theo cách của chúng tôi sao lưu, chúng tôi cần một mô hình nữa để đại diện cho hình ảnh trình giữ chỗ, mô hình này sẽ bao gồm một bộ sưu tập các FlickrImages và FlickrImage hiện tại đã được chọn. Chúng tôi sẽ gọi mô hình này là FlickrBombImage.

var localStorage = (supports_local_storage ())? new Store ("flickrBombImages"): null; var FlickrBombImage = Backbone.Model.extend ({localStorage: localStorage, khởi tạo: function () {_.bindAll (this, 'loadFirstImage'); this.flickrImages = new FlickrImages (); this.flickrImages.fetch (this.get ('keywords'), this.loadFirstImage); this.set (id: this.get ("id")); this.bind ('change: src', this.changeSrc) ;}, changeSrc: function () {this.save ();}, loadFirstImage: function () {if (this.get ('src') === undefined) {this.set ({src: this.flickrImages. first (). image_url ()});}}});

Vì mô hình này chịu trách nhiệm theo dõi hình ảnh hiện được chọn giữa các lần tải trang, nên nó cần biết kho lưu trữ localstorage sẽ sử dụng.Dòng đầu tiên sẽ đảm bảo có hỗ trợ cho localstorage, sau đó tạo cửa hàng mà chúng tôi sẽ sử dụng để lưu giữ hình ảnh đã chọn.

Backbone cho phép chúng ta định nghĩa một hàm .initialize () sẽ được gọi khi một thể hiện của mô hình được tạo. Chúng tôi sử dụng chức năng này trong FlickrBombImage để tạo một phiên bản mới của bộ sưu tập FlickrImages, chuyển các từ khóa sẽ được sử dụng cho hình ảnh này, sau đó tìm nạp hình ảnh từ Flickr.

Hàm .loadFirstImage () đã được chuyển như một lệnh gọi lại để chạy khi hình ảnh đã được tải từ Flickr. Như bạn có thể đoán, chức năng này đặt hình ảnh hiện tại là hình ảnh đầu tiên trong bộ sưu tập từ Flickr. Nó không làm điều này nếu hình ảnh hiện tại đã được thiết lập.

Chúng tôi cũng sẽ sử dụng lệnh gọi lại thuộc tính của Backbone để kích hoạt hàm .changeSrc () khi thuộc tính src của mô hình này thay đổi. Tất cả những gì gọi lại này là gọi .save (), một hàm mô hình Backbone duy trì các thuộc tính của mô hình cho bất kỳ lớp lưu trữ nào đã được triển khai (trong trường hợp của chúng tôi là localstore). Bằng cách này, bất cứ khi nào hình ảnh đã chọn bị thay đổi, nó sẽ được lưu giữ ngay lập tức.

Lớp xem

Bây giờ chúng tôi đã viết tất cả mã phụ trợ (tốt, phụ trợ giao diện người dùng), chúng tôi có thể tập hợp các Chế độ xem lại với nhau. Lượt xem trong Backbone khác một chút so với lượt xem trong các khuôn khổ MVC truyền thống khác. Mặc dù một chế độ xem thường chỉ liên quan đến bản trình bày, nhưng Chế độ xem xương sống cũng chịu trách nhiệm về hành vi. Điều đó có nghĩa là Chế độ xem của bạn không chỉ xác định một thứ gì đó trông như thế nào mà còn xác định những gì nó sẽ làm khi được tương tác với.

Một Chế độ xem thường (nhưng không phải luôn luôn) được gắn với một số dữ liệu và trải qua ba giai đoạn để tạo đánh dấu bản trình bày từ dữ liệu đó:

1. Đối tượng View được khởi tạo và một phần tử trống được tạo.
2. Hàm kết xuất được gọi, tạo ra đánh dấu cho khung nhìn bằng cách chèn nó vào phần tử đã tạo ở bước trước.
3. Phần tử được gắn vào DOM.

Điều này có vẻ như rất nhiều công việc để tạo ra một số đánh dấu và chúng tôi thậm chí chưa đến phần hành vi của Chế độ xem, nhưng điều này rất quan trọng và đây là lý do tại sao. Mỗi khi bạn sửa đổi các phần tử có trong DOM, bạn sẽ kích hoạt một thứ gọi là chỉnh lại trình duyệt. Chỉnh lại là trình duyệt tính toán lại cách mọi thứ trên trang được định vị. Trình duyệt trình duyệt có thể ảnh hưởng xấu đến hiệu suất nếu được gọi trong một sự kiện kéo hoặc thay đổi kích thước, sự kiện này sẽ kích hoạt trong một khoảng thời gian rất ngắn, nhưng tệ hơn, chúng trông rất cẩu thả. Với thao tác trang phức tạp, bạn thực sự có thể thấy các phần tử được thêm vào trang và định vị lại các phần tử có hiệu lực. Tuân theo mô hình khởi tạo, kết xuất và đính kèm của Backbone, bạn đảm bảo một lần chỉnh lại duy nhất và các thay đổi đối với trang sẽ được cảm nhận ngay lập tức, bất kể mức độ phức tạp của thao tác phần tử.

FlickrBombImageView

var FlickrBombImageView = Backbone.View.extend ({tagName: "div", className: "flickrbombContainer", lock: false, template: _.template ('div id = "% = this.image.id.replace (" ", "")%> "... / div> '), khởi tạo: function (options) {_.bindAll (this,' addImage ',' updateSrc ',' setDimentions ',' updateDimentions '); var keywords = options. img.attr ('src') .replace ('flickr: //', ''); this. $ el = $ (this.el); this.image = new FlickrBombImage ({keywords: keywords, id: options. img.attr ('id')}); this.image.flickrImages.bind ('add', this.addImage); this.image.bind ('change: src', this.updateSrc);}, sự kiện: { "click .setupIcon": "clickSetup", "click .flickrbombFlyout a.photo": "selectImage", "click .flickrbombFlyout a.next": "nextFlickrPhotos", "click .flickrbombFlyout a.prev": "prevFlickrPhotos"}, render: function () {$ (this.el) .html (this.template ()); this.image.fetch (); this.resize (); return this;}, ...});

Các chức năng của chế độ xem này đã được bỏ qua cho ngắn gọn, toàn bộ mã nguồn hiện có trên GitHub: github.com/zurb/flickrbomb

Ở trên cùng của Chế độ xem, chúng tôi có một vài thuộc tính cụ thể của Đường trục. tagName và className được sử dụng để xác định thẻ và lớp sẽ được áp dụng cho phần tử của Chế độ xem này. Hãy nhớ rằng bước một của quá trình tạo View là tạo một đối tượng và vì việc tạo đó được xử lý bởi Backbone, chúng ta cần chỉ định phần tử và lớp. Lưu ý rằng Backbone có các giá trị mặc định hợp lý; nếu chúng tôi bỏ qua các thuộc tính này, một div được sử dụng theo mặc định và sẽ không có lớp nào được áp dụng trừ khi bạn chỉ định một lớp.

Thuộc tính mẫu là một quy ước, nhưng không bắt buộc. Chúng tôi đang sử dụng nó ở đây để chỉ định hàm mẫu JavaScript mà chúng tôi sẽ sử dụng để tạo đánh dấu của chúng tôi cho chế độ xem này. Chúng tôi sử dụng hàm _.template () có trong Underscore.js, nhưng bạn có thể sử dụng công cụ tạo khuôn mẫu nào mà bạn thích, chúng tôi sẽ không đánh giá bạn.

Trong hàm .initialize () của chúng tôi, chúng tôi đang lấy chuỗi từ khóa ra khỏi thẻ hình ảnh, sau đó tạo mô hình FlickrBombImage bằng cách sử dụng các từ khóa đó. Chúng tôi cũng ràng buộc hàm .addImage () sẽ được chạy khi FlickrImage được thêm vào bộ sưu tập FlickrImages. Chức năng này sẽ nối FlickrImage mới được thêm vào trình chọn hình ảnh của chúng tôi. Dòng cuối cùng và quan trọng nhất là ràng buộc hàm .updateSrc () để kích hoạt khi FlickrImage hiện đang được chọn bị thay đổi. Khi hình ảnh hiện tại được thay đổi trong mô hình, chức năng này sẽ chạy, cập nhật thuộc tính src của phần tử hình ảnh và CSS thay đổi kích thước và cắt hình ảnh để vừa với kích thước hình ảnh do người dùng chỉ định.

sự kiện: {"click .setupIcon": "clickSetup", "click .flickrbombFlyout a.photo": "selectImage", "click .flickrbombFlyout a.next": "nextFlickrPhotos", "click .flickrbombFlyout a.prev": "prevFlickrPhotos "}

Sau .initialize (), chúng ta có phần hành vi của Chế độ xem. Backbone cung cấp một cách thuận tiện để liên kết các sự kiện bằng cách sử dụng một đối tượng sự kiện. Đối tượng sự kiện sử dụng phương thức jQuery .delegate () để thực hiện liên kết thực tế với phần tử View, để bất kể bạn thực hiện thao tác gì với phần tử bên trong khung nhìn, tất cả các sự kiện liên kết của bạn sẽ vẫn hoạt động. Nó hoạt động giống như jQuery .live (), ngoại trừ việc thay vì ràng buộc các sự kiện với toàn bộ tài liệu, bạn có thể ràng buộc chúng trong phạm vi của bất kỳ phần tử nào. Khóa của mỗi mục nhập trong đối tượng sự kiện bao gồm sự kiện và bộ chọn, giá trị cho biết hàm đó sẽ được liên kết với sự kiện đó. Lưu ý rằng .delegate () không hoạt động với một số sự kiện như submit, hãy xem tài liệu jQuery .live () để biết danh sách đầy đủ các sự kiện được hỗ trợ.

render: function () {$ (this.el) .html (this.template ()); this.image.fetch (); this.resize (); trả lại cái này;}

Cuối cùng, chúng tôi có hàm .render () chịu trách nhiệm tạo đánh dấu của chúng tôi và thực hiện bất kỳ công việc bổ sung nào không thể thực hiện cho đến khi đánh dấu Chế độ xem đã được thêm vào phần tử Chế độ xem. Sau khi kết xuất mẫu, chúng ta cần gọi .fetch () trên FlickrBombImage. .fetch () là một hàm Backbone lấy bản sao mới nhất của mô hình từ lớp bền vững. Nếu chúng ta đã lưu mô hình này trước đây, thì .fetch () sẽ truy xuất dữ liệu đó ngay bây giờ. Sau khi hình ảnh đã được tìm nạp, chúng tôi cần gọi thay đổi kích thước để định vị chính xác.

Căng nhà

Với tất cả các phần đã đặt, tất cả những gì chúng ta cần làm bây giờ là tìm các hình ảnh giữ chỗ trên trang và thay thế chúng bằng các chế độ xem FlickrBombImage được hiển thị.

$ ("img [src ^ = 'flickr: //']") .each (function () {var img = $ (this), flickrBombImageView = new FlickrBombImageView ({img: img}); img.replaceWith (flickrBombImageView. render (). el);});

Đoạn mã nhỏ này cần được chạy ở cuối trang hoặc trong một lệnh gọi lại sẵn sàng cho tài liệu, để đảm bảo rằng nó có thể tìm thấy các hình ảnh trình giữ chỗ mà nó sẽ thay thế. Chúng tôi sử dụng quy ước chỉ định flickr: // [KEYWORD] trong thuộc tính src của thẻ hình ảnh để cho biết thẻ nên được điền bằng hình ảnh từ Flickr. Chúng tôi tìm các phần tử hình ảnh có thuộc tính src phù hợp, tạo một FlickrBombImageView mới, sau đó thay thế hình ảnh bằng thuộc tính của chúng tôi. Chúng tôi lấy một bản sao của hình ảnh gốc và chuyển nó đến FlickrBombView của chúng tôi, để chúng tôi có thể kéo một số tùy chọn cấu hình bổ sung có thể đã được chỉ định trên phần tử.

Kết quả cuối cùng của tất cả những công việc khó khăn đó là một API rất đơn giản cho những người sử dụng thư viện. Họ có thể chỉ cần xác định các thẻ hình ảnh bằng cách sử dụng quy ước flickr: //, thả mã FlickrBomb ở cuối trang của họ và nói rằng họ đã nhận được các hình ảnh giữ chỗ từ Flickr.

Hoạt động tốt với các ứng dụng web lớn

Chúng tôi có một ứng dụng web lớn tên là Notable, được viết mà không cần quan tâm đến việc tạo nội dung phía máy khách. Khi chúng tôi muốn tính phí các phần của turbo ứng dụng bằng cách tạo ra phía máy khách nội dung, chúng tôi đã chọn Backbone. Lý do là giống nhau: chúng tôi muốn một khung công tác nhẹ để giúp giữ cho mã có tổ chức, nhưng không buộc chúng tôi phải suy nghĩ lại toàn bộ ứng dụng.

Chúng tôi đã đưa ra những thay đổi vào đầu năm nay với thành công rực rỡ, và kể từ đó đã ca ngợi những lời khen ngợi của Backbone.

Tài nguyên bổ sung

Còn rất nhiều điều về Backbone so với những gì tôi đã trình bày trong bài viết này, phần C (controller) của MVC (model view controller) dành cho người mới bắt đầu, thực sự là R (bộ định tuyến) trong phiên bản mới nhất. Và tất cả đều được đề cập trong tài liệu Backbone, một buổi sáng thứ Bảy nhẹ nhàng, bạn đọc:
documentcloud.github.com/backbone/

Nếu các hướng dẫn truyền thống hơn là sở thích của bạn, thì hãy xem đoạn mã được tài liệu hóa rất tốt của ứng dụng todo này được viết bằng Backbone:
documentcloud.github.com/backbone/docs/todos.html

ĐọC Sách NhiềU NhấT
Tại sao thiết kế tuyệt vời có thể vượt qua bất cứ điều gì
Phát HiệN

Tại sao thiết kế tuyệt vời có thể vượt qua bất cứ điều gì

au khi khám phá chủ đề thiết kế như một tác nhân cho ự thay đổi tích cực vào ngày đầu tiên, ngày thứ hai của Lễ hội thiết kế Cheltenham đã mở rộng lĩ...
Mã hóa cho trẻ em: 7 đồ chơi hàng đầu
Phát HiệN

Mã hóa cho trẻ em: 7 đồ chơi hàng đầu

Việc viết mã cho trẻ em có vẻ hơi phức tạp, nhưng bạn ẽ không bao giờ ớm bắt đầu. Có một ố đồ chơi tuyệt vời, vui nhộn xung quanh được thiết kế để giới thiệu cho trẻ cách tư d...
10 trường hợp iPad hàng đầu cho các nhà thiết kế
Phát HiệN

10 trường hợp iPad hàng đầu cho các nhà thiết kế

Vì vậy, bạn đã chi một khoản tài chính nhỏ cho một chiếc iPad mới và bây giờ bạn muốn một thứ gì đó chắc chắn để bảo vệ nó, nhưng cũng là thứ thể hiện...