Thứ năm, 12/12/2019 | 00:00 GMT+7

Cách phát triển một trình tải lên tệp tương tác với JavaScript và Canvas

Ta có thể thực hiện các tương tác trên một trang web hoặc ứng dụng web tốt hay thú vị đến mức nào? Sự thật là hầu hết có thể tốt hơn ta ngày nay. Ví dụ, ai sẽ không muốn sử dụng một ứng dụng như thế này:

Cú sút lừa bóng của Jakub Antalík
Tín dụng: Jakub Antalik khi rê bóng

Trong hướng dẫn này, ta sẽ thấy cách triển khai một thành phần sáng tạo để tải file lên, sử dụng làm nguồn cảm hứng cho hoạt ảnh trước đó của Jakub Antalík . Ý tưởng là mang lại phản hồi trực quan tốt hơn về những gì xảy ra với file sau khi bị xóa.

Ta sẽ chỉ tập trung vào việc triển khai các tương tác dragdrop và một số hoạt ảnh, mà không thực sự triển khai tất cả các logic cần thiết để thực sự tải file lên server và sử dụng thành phần trong production .

Đây là thành phần của ta sẽ trông như thế nào:

Tương tác Tải lên Quảng cáo

Bạn có thể xem bản demo trực tiếp hoặc chơi với mã trong Codepen . Nhưng nếu bạn cũng muốn biết nó hoạt động như thế nào, hãy tiếp tục đọc.

Trong phần hướng dẫn, ta sẽ thấy hai khía cạnh chính:

  • Ta sẽ học cách triển khai một hệ thống hạt đơn giản bằng Javascript và Canvas.
  • Ta sẽ triển khai mọi thứ cần thiết để xử lý các sự kiện dragdrop .

Ngoài các công nghệ thông thường (HTML, CSS, Javascript), để viết mã thành phần của ta , ta sẽ sử dụng thư viện hoạt hình nhẹ anime.js .

Bước 1 - Tạo cấu trúc HTML

Trong trường hợp này, cấu trúc HTML của ta sẽ khá cơ bản:

<!-- Form to upload the files --> <form class="upload" method="post" action="" enctype="multipart/form-data" novalidate="">     <!-- The `input` of type `file` -->     <input class="upload__input" name="files[]" type="file" multiple=""/>     <!-- The `canvas` element to draw the particles -->     <canvas class="upload__canvas"></canvas>     <!-- The upload icon -->     <div class="upload__icon"><svg viewBox="0 0 470 470"><path d="m158.7 177.15 62.8-62.8v273.9c0 7.5 6 13.5 13.5 13.5s13.5-6 13.5-13.5v-273.9l62.8 62.8c2.6 2.6 6.1 4 9.5 4 3.5 0 6.9-1.3 9.5-4 5.3-5.3 5.3-13.8 0-19.1l-85.8-85.8c-2.5-2.5-6-4-9.5-4-3.6 0-7 1.4-9.5 4l-85.8 85.8c-5.3 5.3-5.3 13.8 0 19.1 5.2 5.2 13.8 5.2 19 0z"></path></svg></div> </form> 

Như bạn thấy , ta chỉ cần một phần tử form và một input loại file để cho phép tải file lên server . Trong thành phần của ta , ta cũng cần một phần tử canvas để vẽ các hạt và biểu tượng SVG.

Lưu ý để sử dụng một thành phần như thế này trong production , bạn phải điền thuộc tính action vào biểu mẫu và có thể thêm một phần tử label cho đầu vào, v.v.

Bước 2 - Thêm kiểu CSS

Ta sẽ sử dụng SCSS làm bộ tiền xử lý CSS, nhưng các kiểu ta đang sử dụng rất gần với CSS thuần túy và chúng khá đơn giản.

Hãy bắt đầu bằng cách định vị các phần tử formcanvas , trong số các kiểu cơ bản khác:

// Position `form` and `canvas` full width and height .upload, .upload__canvas {   position: absolute;   left: 0;   top: 0;   width: 100%;   height: 100%; }  // Position the `canvas` behind all other elements .upload__canvas {   z-index: -1; }  // Hide the file `input` .upload__input {   display: none; } 

Bây giờ ta hãy xem các kiểu cần thiết cho form của ta , cho cả trạng thái ban đầu (ẩn) và khi nó đang hoạt động ( user đang kéo file để tải lên). Mã đã được comment đầy đủ để hiểu rõ hơn:

// Styles for the upload `form` .upload {   z-index: 1; // should be the higher `z-index`   // Styles for the `background`   background-color: rgba(4, 72, 59, 0.8);   background-image: radial-gradient(ellipse at 50% 120%, rgba(4, 72, 59, 1) 10%, rgba(4, 72, 59, 0) 40%);   background-position: 0 300px;   background-repeat: no-repeat;   // Hide it by default   opacity: 0;   visibility: hidden;   // Transition   transition: 0.5s;    // Upload overlay, that prevent the event `drag-leave` to be triggered while dragging over inner elements   &:after {     position: absolute;     content: '';     left: 0;     top: 0;     width: 100%;     height: 100%;   } }  // Styles applied while files are being dragging over the screen .upload--active {   // Translate the `radial-gradient`   background-position: 0 0;   // Show the upload component   opacity: 1;   visibility: visible;   // Only transition `opacity`, preventing issues with `visibility`   transition-property: opacity; } 

Cuối cùng, hãy xem các kiểu đơn giản mà ta đã áp dụng cho biểu tượng tải lên:

// Styles for the icon .upload__icon {   position: relative;   left: calc(50% - 40px);   top: calc(50% - 40px);   width: 80px;   height: 80px;   padding: 15px;   border-radius: 100%;   background-color: #EBF2EA;    path {     fill: rgba(4, 72, 59, 0.8);   } } 

Bây giờ thành phần của ta trông giống như ta muốn, vì vậy ta đã sẵn sàng thêm tính tương tác với Javascript.

Bước 3 - Phát triển một hệ thống hạt

Trước khi triển khai chức năng dragdrop , hãy xem cách ta có thể triển khai một hệ thống hạt.

Trong hệ thống hạt của ta , mỗi hạt sẽ là một Object Javascript đơn giản với các tham số cơ bản để xác định cách hạt hoạt động. Và tất cả các hạt sẽ được lưu trữ trong một Array , trong đó mã của ta được gọi là particles .

Sau đó, thêm một hạt mới vào hệ thống của ta là việc tạo một Object Javascrit mới và thêm nó vào mảng particles . Kiểm tra các comment để bạn hiểu mục đích của từng thuộc tính:

// Create a new particle function createParticle(options) {     var o = options || {};     particles.push({         'x': o.x, // particle position in the `x` axis         'y': o.y, // particle position in the `y` axis         'vx': o.vx, // in every update (animation frame) the particle will be translated this amount of pixels in `x` axis         'vy': o.vy, // in every update (animation frame) the particle will be translated this amount of pixels in `y` axis         'life': 0, // in every update (animation frame) the life will increase         'death': o.death || Math.random() * 200, // consider the particle dead when the `life` reach this value         'size': o.size || Math.floor((Math.random() * 2) + 1) // size of the particle     }); } 

Bây giờ ta đã xác định cấu trúc cơ bản của hệ thống hạt của bạn , ta cần một hàm vòng lặp, cho phép ta thêm các hạt mới, cập nhật chúng và vẽ chúng trên canvas trong mỗi khung hoạt hình. Thông tin như thế này:

// Loop to redraw the particles on every frame function loop() {     addIconParticles(); // add new particles for the upload icon     updateParticles(); // update all particles     renderParticles(); // clear `canvas` and draw all particles     iconAnimationFrame = requestAnimationFrame(loop); // loop } 

Bây giờ ta hãy xem cách ta đã định nghĩa tất cả các hàm mà ta gọi bên trong vòng lặp. Như mọi khi, hãy chú ý đến các comment :

// Add new particles for the upload icon function addIconParticles() {     iconRect = uploadIcon.getBoundingClientRect(); // get icon dimensions     var i = iconParticlesCount; // how many particles we should add?     while (i--) {         // Add a new particle         createParticle({             x: iconRect.left + iconRect.width / 2 + rand(iconRect.width - 10), // position the particle along the icon width in the `x` axis             y: iconRect.top + iconRect.height / 2, // position the particle centered in the `y` axis             vx: 0, // the particle will not be moved in the `x` axis             vy: Math.random() * 2 * iconParticlesCount // value to move the particle in the `y` axis, greater is faster         });     } }  // Update the particles, removing the dead ones function updateParticles() {     for (var i = 0; i < particles.length; i++) {         if (particles[i].life > particles[i].death) {             particles.splice(i, 1);         } else {             particles[i].x += particles[i].vx;             particles[i].y += particles[i].vy;             particles[i].life++;         }     } }  // Clear the `canvas` and redraw every particle (rect) function renderParticles() {     ctx.clearRect(0, 0, canvasWidth, canvasHeight);     for (var i = 0; i < particles.length; i++) {         ctx.fillStyle = 'rgba(255, 255, 255, ' + (1 - particles[i].life / particles[i].death) + ')';         ctx.fillRect(particles[i].x, particles[i].y, particles[i].size, particles[i].size);     } } 

Và ta đã sẵn sàng hệ thống hạt của bạn , nơi ta có thể thêm các hạt mới xác định các tùy chọn ta muốn và vòng lặp sẽ chịu trách nhiệm thực hiện hoạt ảnh.

Thêm hoạt ảnh cho biểu tượng tải lên

Bây giờ, hãy xem cách ta chuẩn bị cho biểu tượng tải lên thành hoạt ảnh:

// Add 100 particles for the icon (without render), so the animation will not look empty at first function initIconParticles() {     var iconParticlesInitialLoop = 100;     while (iconParticlesInitialLoop--) {         addIconParticles();         updateParticles();     } } initIconParticles();  // Alternating animation for the icon to translate in the `y` axis function initIconAnimation() {     iconAnimation = anime({         targets: uploadIcon,         translateY: -10,         duration: 800,         easing: 'easeInOutQuad',         direction: 'alternate',         loop: true,         autoplay: false // don't execute the animation yet, only on `drag` events (see later)     }); } initIconAnimation(); 

Với mã trước đó, ta chỉ cần một số chức năng khác để tạm dừng hoặc tiếp tục hoạt ảnh của biểu tượng tải lên, nếu thích hợp:

// Play the icon animation (`translateY` and particles) function playIconAnimation() {     if (!playingIconAnimation) {         playingIconAnimation = true;         iconAnimation.play();         iconAnimationFrame = requestAnimationFrame(loop);     } }  // Pause the icon animation (`translateY` and particles) function pauseIconAnimation() {     if (playingIconAnimation) {         playingIconAnimation = false;         iconAnimation.pause();         cancelAnimationFrame(iconAnimationFrame);     } } 

Bước 4 - Thêm chức năng kéo và thả

Sau đó, ta có thể bắt đầu thêm chức năng dragdrop để tải file lên. Hãy bắt đầu bằng cách ngăn chặn các hành vi không mong muốn cho mỗi sự kiện liên quan:

// Preventing the unwanted behaviours ['drag', 'dragstart', 'dragend', 'dragover', 'dragenter', 'dragleave', 'drop'].forEach(function (event) {     document.addEventListener(event, function (e) {         e.preventDefault();         e.stopPropagation();     }); }); 

Bây giờ ta sẽ xử lý các sự kiện kiểu drag , nơi ta sẽ kích hoạt form để nó được hiển thị và ta sẽ phát các hình ảnh động cho biểu tượng tải lên:

// Show the upload component on `dragover` and `dragenter` events ['dragover', 'dragenter'].forEach(function (event) {     document.addEventListener(event, function () {         if (!animatingUpload) {             uploadForm.classList.add('upload--active');             playIconAnimation();         }     }); }); 

Trong trường hợp user rời khỏi khu vực drop , ta chỉ cần ẩn form và tạm dừng các hoạt ảnh cho biểu tượng tải lên:

// Hide the upload component on `dragleave` and `dragend` events ['dragleave', 'dragend'].forEach(function (event) {     document.addEventListener(event, function () {         if (!animatingUpload) {             uploadForm.classList.remove('upload--active');             pauseIconAnimation();         }     }); }); 

Và cuối cùng sự kiện quan trọng nhất mà ta phải xử lý là sự kiện drop , bởi vì đó sẽ là nơi ta lấy các file mà user đã drop, ta sẽ thực thi các hoạt ảnh tương ứng và nếu đây là một thành phần đầy đủ chức năng, ta sẽ tải lên file tới server thông qua AJAX.

// Handle the `drop` event document.addEventListener('drop', function (e) {     if (!animatingUpload) { // If no animation in progress         droppedFiles = e.dataTransfer.files; // the files that were dropped         filesCount = droppedFiles.length > 3 ? 3 : droppedFiles.length; // the number of files (1-3) to perform the animations          if (filesCount) {             animatingUpload = true;              // Add particles for every file loaded (max 3), also staggered (increasing delay)             var i = filesCount;             while (i--) {                 addParticlesOnDrop(e.pageX + (i ? rand(100) : 0), e.pageY + (i ? rand(100) : 0), 200 * i);             }              // Hide the upload component after the animation             setTimeout(function () {                 uploadForm.classList.remove('upload--active');             }, 1500 + filesCount * 150);              // Here is the right place to call something like:             // triggerFormSubmit();             // A function to actually upload the files to the server          } else { // If no files where dropped, just hide the upload component             uploadForm.classList.remove('upload--active');             pauseIconAnimation();         }     } }); 

Trong đoạn mã trước, ta đã thấy rằng hàm addParticlesOnDrop được gọi, có nhiệm vụ thực thi hoạt ảnh hạt từ nơi các file được thả xuống. Hãy xem cách ta có thể triển khai chức năng này:

// Create a new particles on `drop` event function addParticlesOnDrop(x, y, delay) {     // Add a few particles when the `drop` event is triggered     var i = delay ? 0 : 20; // Only add extra particles for the first item dropped (no `delay`)     while (i--) {         createParticle({             x: x + rand(30),             y: y + rand(30),             vx: rand(2),             vy: rand(2),             death: 60         });     }      // Now add particles along the way where the user `drop` the files to the icon position     // Learn more about this kind of animation in the `anime.js` documentation     anime({         targets: {x: x, y: y},         x: iconRect.left + iconRect.width / 2,         y: iconRect.top + iconRect.height / 2,         duration: 500,         delay: delay || 0,         easing: 'easeInQuad',         run: function (anim) {             var target = anim.animatables[0].target;             var i = 10;             while (i--) {                 createParticle({                     x: target.x + rand(30),                     y: target.y + rand(30),                     vx: rand(2),                     vy: rand(2),                     death: 60                 });             }         },         complete: uploadIconAnimation // call the second part of the animation     }); } 

Cuối cùng, khi các phần tử đến vị trí của biểu tượng, ta phải di chuyển biểu tượng lên trên, tạo cảm giác rằng các file đang được tải lên:

// Translate and scale the upload icon function uploadIconAnimation() {     iconParticlesCount += 2; // add more particles per frame, to get a speed up feeling     anime.remove(uploadIcon); // stop current animations     // Animate the icon using `translateY` and `scale`     iconAnimation = anime({         targets: uploadIcon,         translateY: {             value: -canvasHeight / 2 - iconRect.height,             duration: 1000,             easing: 'easeInBack'         },         scale: {             value: '+=0.1',             duration: 2000,             elasticity: 800         },         complete: function () {             // reset the icon and all animation variables to its initial state             setTimeout(resetAll, 0);         }     }); } 

Để kết thúc, ta phải triển khai hàm resetAll , hàm này sẽ đặt lại biểu tượng và tất cả các biến về trạng thái ban đầu. Ta cũng phải cập nhật kích thước canvas và đặt lại thành phần trên sự kiện resize . Nhưng để không thực hiện hướng dẫn này lâu hơn nữa, ta đã không bao gồm những điều này và các chi tiết nhỏ khác, mặc dù bạn có thể kiểm tra mã hoàn chỉnh trong kho lưu trữ Github .

Kết luận

Và cuối cùng thành phần của ta đã hoàn thành! Hãy cùng xem:

Tương tác Tải lên Quảng cáo

Bạn có thể kiểm tra bản demo trực tiếp , chơi với mã trên Codepen hoặc nhận mã đầy đủ trên Github .

Trong suốt hướng dẫn, ta đã thấy cách tạo một hệ thống hạt đơn giản, cũng như xử lý các sự kiện dragdrop để triển khai thành phần tải lên file bắt mắt.

Lưu ý thành phần này chưa sẵn sàng để sử dụng trong production . Trong trường hợp bạn muốn hoàn thành việc triển khai để làm cho nó hoạt động đầy đủ, tôi khuyên bạn nên xem hướng dẫn tuyệt vời này trong CSS Tricks .


Tags:

Các tin liên quan

Cách gói một gói JavaScript Vanilla để sử dụng trong React
2019-12-12
Cách sử dụng map (), filter () và Reduce () trong JavaScript
2019-12-12
Giải thích về lập trình chức năng JavaScript: Ứng dụng một phần và làm xoăn
2019-12-12
Giới thiệu về Closures và Currying trong JavaScript
2019-12-12
Bắt đầu với các hàm mũi tên ES6 trong JavaScript
2019-12-12
Cách đếm số nguyên âm trong một chuỗi văn bản bằng thuật toán JavaScript
2019-12-12
Cách sử dụng phép gán cấu trúc hủy trong JavaScript
2019-12-12
Giải thích về lập trình chức năng JavaScript: Kết hợp & truyền tải
2019-12-12
Cách sử dụng .every () và .some () để điều khiển mảng JavaScript
2019-12-12
Chuyển đổi Mảng sang Chuỗi trong JavaScript
2019-12-05