介绍
我們能在網站或網頁應用程式上進行互動有多麼愉快或有趣?事實是,大多數人可能比我們今天做得更好。
Credit: Jakub Antalik on dribble
在本教程中,我们将看到如何实施一个创意组件来上传文件,使用以前的灵感(Jakov Antalík的动画)(https://dribbble.com/shots/4174256-Drag-and-Drop-to-upload)。
我们将专注于实现拖放
和拖放
互动和一些动画,而不是实际上实现所有必要的逻辑,实际上将文件上传到服务器并在生产中使用该组件。
我们的组件将是这样的:
你可以看到 直播演示或玩用 代码在 Codepen,但如果你也想知道它是如何工作的,只需继续阅读。
在教程中,我们将看到两个主要方面:
- 我们将学习如何使用JavaScript和Canvas实现简单的粒子系统。
- 我们将实现处理
拖动
和放弃
事件所需的一切。
除了通常的技术(HTML,CSS,JavaScript),我们还将使用轻量级动画库(Anime.js)。
第1步:创建HTML结构
在这种情况下,我们的HTML结构将是相当基本的:
1<!-- Form to upload the files -->
2<form class="upload" method="post" action="" enctype="multipart/form-data" novalidate="">
3 <!-- The `input` of type `file` -->
4 <input class="upload__input" name="files[]" type="file" multiple=""/>
5 <!-- The `canvas` element to draw the particles -->
6 <canvas class="upload__canvas"></canvas>
7 <!-- The upload icon -->
8 <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>
9</form>
正如你所看到的,我们只需要一个形式
元素和一个文件
类型的输入
,以便将文件上传到服务器上。
请记住,要在生产中使用这样的组件,您必须在表单中填写行动
属性,并可能为输入添加一个标签
元素等。
步骤 2 – 添加 CSS 风格
我们将使用SCSS作为CSS预处理器,但我们正在使用的风格非常接近于简单的CSS,并且非常简单。
让我们从其他基本风格中定位形状
和面具
元素开始:
1// Position `form` and `canvas` full width and height
2.upload, .upload__canvas {
3 position: absolute;
4 left: 0;
5 top: 0;
6 width: 100%;
7 height: 100%;
8}
9
10// Position the `canvas` behind all other elements
11.upload__canvas {
12 z-index: -1;
13}
14
15// Hide the file `input`
16.upload__input {
17 display: none;
18}
现在让我们看看我们的形式
所需的样式,无论是初始状态(隐藏)还是活动状态(用户正在拖动文件上传)。
1// Styles for the upload `form`
2.upload {
3 z-index: 1; // should be the higher `z-index`
4 // Styles for the `background`
5 background-color: rgba(4, 72, 59, 0.8);
6 background-image: radial-gradient(ellipse at 50% 120%, rgba(4, 72, 59, 1) 10%, rgba(4, 72, 59, 0) 40%);
7 background-position: 0 300px;
8 background-repeat: no-repeat;
9 // Hide it by default
10 opacity: 0;
11 visibility: hidden;
12 // Transition
13 transition: 0.5s;
14
15 // Upload overlay, that prevent the event `drag-leave` to be triggered while dragging over inner elements
16 &:after {
17 position: absolute;
18 content: '';
19 left: 0;
20 top: 0;
21 width: 100%;
22 height: 100%;
23 }
24}
25
26// Styles applied while files are being dragging over the screen
27.upload--active {
28 // Translate the `radial-gradient`
29 background-position: 0 0;
30 // Show the upload component
31 opacity: 1;
32 visibility: visible;
33 // Only transition `opacity`, preventing issues with `visibility`
34 transition-property: opacity;
35}
最后,让我们看看我们已经应用于上传图标的简单风格:
1// Styles for the icon
2.upload__icon {
3 position: relative;
4 left: calc(50% - 40px);
5 top: calc(50% - 40px);
6 width: 80px;
7 height: 80px;
8 padding: 15px;
9 border-radius: 100%;
10 background-color: #EBF2EA;
11
12 path {
13 fill: rgba(4, 72, 59, 0.8);
14 }
15}
现在我们的组件看起来像我们想要的,所以我们准备用JavaScript添加互动性。
步骤三:开发粒子系统
在实施拖
和放
功能之前,让我们看看如何实现粒子系统。
在我们的粒子系统中,每个粒子将是一个简单的JavaScript对象
,具有基本参数来定义粒子应该如何行为,所有粒子将存储在一个阵列
,在我们的代码中被称为粒子
。
然后,将新粒子添加到我们的系统是创建一个新的Javascrit对象
,并将其添加到粒子
阵列中。
1// Create a new particle
2function createParticle(options) {
3 var o = options || {};
4 particles.push({
5 'x': o.x, // particle position in the `x` axis
6 'y': o.y, // particle position in the `y` axis
7 'vx': o.vx, // in every update (animation frame) the particle will be translated this amount of pixels in `x` axis
8 'vy': o.vy, // in every update (animation frame) the particle will be translated this amount of pixels in `y` axis
9 'life': 0, // in every update (animation frame) the life will increase
10 'death': o.death || Math.random() * 200, // consider the particle dead when the `life` reach this value
11 'size': o.size || Math.floor((Math.random() * 2) + 1) // size of the particle
12 });
13}
现在我们已经定义了我们粒子系统的基本结构,我们需要一个循环函数,这使我们能够添加新的粒子,更新它们,并在每个动画框中绘制它们的面板
。
1// Loop to redraw the particles on every frame
2function loop() {
3 addIconParticles(); // add new particles for the upload icon
4 updateParticles(); // update all particles
5 renderParticles(); // clear `canvas` and draw all particles
6 iconAnimationFrame = requestAnimationFrame(loop); // loop
7}
现在让我们看看我们如何定义了我们在循环中呼叫的所有函数,像往常一样,注意评论:
1// Add new particles for the upload icon
2function addIconParticles() {
3 iconRect = uploadIcon.getBoundingClientRect(); // get icon dimensions
4 var i = iconParticlesCount; // how many particles we should add?
5 while (i--) {
6 // Add a new particle
7 createParticle({
8 x: iconRect.left + iconRect.width / 2 + rand(iconRect.width - 10), // position the particle along the icon width in the `x` axis
9 y: iconRect.top + iconRect.height / 2, // position the particle centered in the `y` axis
10 vx: 0, // the particle will not be moved in the `x` axis
11 vy: Math.random() * 2 * iconParticlesCount // value to move the particle in the `y` axis, greater is faster
12 });
13 }
14}
15
16// Update the particles, removing the dead ones
17function updateParticles() {
18 for (var i = 0; i < particles.length; i++) {
19 if (particles[i].life > particles[i].death) {
20 particles.splice(i, 1);
21 } else {
22 particles[i].x += particles[i].vx;
23 particles[i].y += particles[i].vy;
24 particles[i].life++;
25 }
26 }
27}
28
29// Clear the `canvas` and redraw every particle (rect)
30function renderParticles() {
31 ctx.clearRect(0, 0, canvasWidth, canvasHeight);
32 for (var i = 0; i < particles.length; i++) {
33 ctx.fillStyle = 'rgba(255, 255, 255, ' + (1 - particles[i].life / particles[i].death) + ')';
34 ctx.fillRect(particles[i].x, particles[i].y, particles[i].size, particles[i].size);
35 }
36}
我们有我们的粒子系统准备好了,我们可以添加新的粒子来定义我们想要的选项,循环将负责执行动画。
添加上传图标的动画
现在让我们看看我们如何准备上传图标进行动画:
1// Add 100 particles for the icon (without render), so the animation will not look empty at first
2function initIconParticles() {
3 var iconParticlesInitialLoop = 100;
4 while (iconParticlesInitialLoop--) {
5 addIconParticles();
6 updateParticles();
7 }
8}
9initIconParticles();
10
11// Alternating animation for the icon to translate in the `y` axis
12function initIconAnimation() {
13 iconAnimation = anime({
14 targets: uploadIcon,
15 translateY: -10,
16 duration: 800,
17 easing: 'easeInOutQuad',
18 direction: 'alternate',
19 loop: true,
20 autoplay: false // don't execute the animation yet, only on `drag` events (see later)
21 });
22}
23initIconAnimation();
在上面的代码中,我们只需要一些其他功能来暂停或恢复上传图标的动画,如有可能:
1// Play the icon animation (`translateY` and particles)
2function playIconAnimation() {
3 if (!playingIconAnimation) {
4 playingIconAnimation = true;
5 iconAnimation.play();
6 iconAnimationFrame = requestAnimationFrame(loop);
7 }
8}
9
10// Pause the icon animation (`translateY` and particles)
11function pauseIconAnimation() {
12 if (playingIconAnimation) {
13 playingIconAnimation = false;
14 iconAnimation.pause();
15 cancelAnimationFrame(iconAnimationFrame);
16 }
17}
步骤 4 – 添加拖放功能
然后我们可以开始添加拖放
和拖放
功能来上传文件。
1// Preventing the unwanted behaviours
2['drag', 'dragstart', 'dragend', 'dragover', 'dragenter', 'dragleave', 'drop'].forEach(function (event) {
3 document.addEventListener(event, function (e) {
4 e.preventDefault();
5 e.stopPropagation();
6 });
7});
现在我们将处理类型的拖动
事件,在那里我们将激活形式
,以便显示它,我们将播放上传图标的动画:
1// Show the upload component on `dragover` and `dragenter` events
2['dragover', 'dragenter'].forEach(function (event) {
3 document.addEventListener(event, function () {
4 if (!animatingUpload) {
5 uploadForm.classList.add('upload--active');
6 playIconAnimation();
7 }
8 });
9});
如果用户离开放下
区域,我们只会再次隐藏表格
,并暂停上传图标的动画:
1// Hide the upload component on `dragleave` and `dragend` events
2['dragleave', 'dragend'].forEach(function (event) {
3 document.addEventListener(event, function () {
4 if (!animatingUpload) {
5 uploadForm.classList.remove('upload--active');
6 pauseIconAnimation();
7 }
8 });
9});
最后,我们必须处理的最重要的事件是下降
事件,因为这将是我们获得用户丢失的文件的地方,我们将执行相应的动画,如果这是一个功能齐全的组件,我们会通过AJAX将文件上传到服务器。
1// Handle the `drop` event
2document.addEventListener('drop', function (e) {
3 if (!animatingUpload) { // If no animation in progress
4 droppedFiles = e.dataTransfer.files; // the files that were dropped
5 filesCount = droppedFiles.length > 3 ? 3 : droppedFiles.length; // the number of files (1-3) to perform the animations
6
7 if (filesCount) {
8 animatingUpload = true;
9
10 // Add particles for every file loaded (max 3), also staggered (increasing delay)
11 var i = filesCount;
12 while (i--) {
13 addParticlesOnDrop(e.pageX + (i ? rand(100) : 0), e.pageY + (i ? rand(100) : 0), 200 * i);
14 }
15
16 // Hide the upload component after the animation
17 setTimeout(function () {
18 uploadForm.classList.remove('upload--active');
19 }, 1500 + filesCount * 150);
20
21 // Here is the right place to call something like:
22 // triggerFormSubmit();
23 // A function to actually upload the files to the server
24
25 } else { // If no files where dropped, just hide the upload component
26 uploadForm.classList.remove('upload--active');
27 pauseIconAnimation();
28 }
29 }
30});
在之前的代码片段中,我们看到称为addParticlesOnDrop
的函数,该函数负责执行粒子动画,从那里丢掉了文件。
1// Create a new particles on `drop` event
2function addParticlesOnDrop(x, y, delay) {
3 // Add a few particles when the `drop` event is triggered
4 var i = delay ? 0 : 20; // Only add extra particles for the first item dropped (no `delay`)
5 while (i--) {
6 createParticle({
7 x: x + rand(30),
8 y: y + rand(30),
9 vx: rand(2),
10 vy: rand(2),
11 death: 60
12 });
13 }
14
15 // Now add particles along the way where the user `drop` the files to the icon position
16 // Learn more about this kind of animation in the `anime.js` documentation
17 anime({
18 targets: {x: x, y: y},
19 x: iconRect.left + iconRect.width / 2,
20 y: iconRect.top + iconRect.height / 2,
21 duration: 500,
22 delay: delay || 0,
23 easing: 'easeInQuad',
24 run: function (anim) {
25 var target = anim.animatables[0].target;
26 var i = 10;
27 while (i--) {
28 createParticle({
29 x: target.x + rand(30),
30 y: target.y + rand(30),
31 vx: rand(2),
32 vy: rand(2),
33 death: 60
34 });
35 }
36 },
37 complete: uploadIconAnimation // call the second part of the animation
38 });
39}
最后,当粒子达到图标的位置时,我们必须向上移动图标,产生文件正在上传的印象:
1// Translate and scale the upload icon
2function uploadIconAnimation() {
3 iconParticlesCount += 2; // add more particles per frame, to get a speed up feeling
4 anime.remove(uploadIcon); // stop current animations
5 // Animate the icon using `translateY` and `scale`
6 iconAnimation = anime({
7 targets: uploadIcon,
8 translateY: {
9 value: -canvasHeight / 2 - iconRect.height,
10 duration: 1000,
11 easing: 'easeInBack'
12 },
13 scale: {
14 value: '+=0.1',
15 duration: 2000,
16 elasticity: 800
17 },
18 complete: function () {
19 // reset the icon and all animation variables to its initial state
20 setTimeout(resetAll, 0);
21 }
22 });
23}
最后,我们必须实施resetAll
函数,该函数将图标和所有变量恢复到其初始状态,我们还必须更新面板
大小,并在resize
事件中重新设置组件,但为了不再使用本教程,我们没有包括这些和其他小细节,尽管您可以在Github存储中检查完整的代码(https://github.com/lmgonzalves/creative-upload)。
结论
最后,我们的组件已经完成了!让我们看看:
你可以查看 直播演示,玩与 代码在 Codepen,或得到 完整的代码在Github。
在整个教程中,我们看到如何创建一个简单的粒子系统,以及处理拖动
和下降
事件,以实现令人眼花缭乱的文件上传组件。
请记住,此组件尚未准备好用于生产,如果您想完成实现以使其完全功能,我建议您检查此 优秀的CSS技巧教程。