使用JavaScript和Canvas开发广告素材上载互动

How nice or fun can we do the interactions on a website or web application? The truth is that most could be better than we do today. For example, who would not want to use an application like this?

我们可以在网站或Web应用程序上进行互动有多有趣? 事实是,大多数情况可能会比今天更好。 例如,谁不想使用这样的应用程序?

Dribble Shot by Jakub Antalík

I would upload things there, again and again, just to have the pleasure of watching the animation :)

我会一遍又一遍地上传东西,只是为了欣赏动画而感到高兴:)

Well in this tutorial we will see how to implement a creative component to upload files, using as inspiration the previous animation by Jakub Antalík. The Idea is to bring better visual feedback around what happens with the file after is dropped.

在本教程中,我们将看到如何实现创意组件,并以JakubAntalík的上一个动画为灵感。 这个想法是关于删除文件后如何处理带来更好的视觉反馈。

We will be focusing only on implementing the drag and drop interactions and some animations, without actually implementing all the necessary logic to actually upload the files to the server and use the component in production.

我们将只注重实现dragdrop的相互作用和一些动画,而无需实际执行所有必要的逻辑实际的文件上传到服务器,并在生产中使用的组件。

This is what our component will look like:

这是我们的组件的外观:

Creative Upload Interaction

You can see the live demo or play with the code in Codepen. But if you also want to know how it works, just keep reading :)

您可以观看现场演示在Codepen中代码 。 但是,如果您还想知道其工作原理,请继续阅读:)

During the tutorial we will be seeing two main aspects:

在本教程中,我们将看到两个主要方面:

  • We will learn how to implement a simple particle system using Javascript and Canvas.

    我们将学习如何使用Javascript和Canvas实现简单的粒子系统。
  • We will implement everything necessary to handle drag and drop events to upload files.

    我们将实施一切必要措施来处理dragdrop事件来上传文件。

In addition to the usual technologies (HTML, CSS, Javascript), to code our component we will use the lightweight animation library anime.js.

除了常用的技术(HTML,CSS,Javascript)之外,我们还将使用轻量级动画库anime.js来编码我们的组件。

So without further ado, let's start!

因此,事不宜迟,让我们开始吧!

HTML结构 ( HTML structure )

In this case our HTML structure will be quite simple:

在这种情况下,我们HTML结构将非常简单:

<!-- 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>

As you can see, we only need a form element and a file type input to allow the upload of files to the server. In our component we also need a canvas element to draw the particles and an SVG icon.

如您所见,我们只需要一个form元素和一个file类型input即可将文件上传到服务器。 在我们的组件中,我们还需要一个canvas元素来绘制粒子和一个SVG图标。

Keep in mind that to use a component like this in production, you must fill in the action attribute in the form, perhaps add a label element for the input, etc.

请记住,要在生产中使用这样的组件,必须在表单中填写action属性,也许为输入添加一个label元素,等等。

添加样式 ( Adding styles )

We will be using SCSS as the CSS preprocessor, but the styles we are using are very close to being plain CSS and they are quite simple.

我们将使用SCSS作为CSS预处理程序,但是我们使用的样式非常接近纯CSS,而且非常简单。

Let's start by positioning the form and canvas elements, among other basic styles:

让我们从canvas formcanvas元素以及其他基本样式开始:

// 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;
}

Now let's see the styles needed for our form, both for the initial state (hidden) and for when it is active (the user is dragging files to upload). The code has been commented exhaustively for a better understanding:

现在,让我们看一下form所需的样式,包括初始状态(隐藏)和活动状态(用户拖动文件进行上传)所需的样式。 该代码已被详尽注释以更好地理解:

// 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;
}

Finally, let's look at the simple styles that we have applied to the upload icon:

最后,让我们看一下已应用于上传图标的简单样式:

// 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);
  }
}

Now our component looks like we want, so we're ready to add interactivity with Javascript.

现在我们的组件看起来像我们想要的,因此我们准备添加与Javascript的交互性。

开发一个简单的粒子系统 ( Developing a simple particle system )

Before implementing the drag and drop functionality, let's see how we can implement a simple particle system.

实施之前dragdrop功能,让我们看看如何可以实现一个简单的粒子系统。

In our particle system, each particle will be a simple Javascript Object with basic parameters to define how the particle should behave. And all the particles will be stored in an Array, which in our code is particles.

在我们的粒子系统中,每个粒子将是一个具有基本参数的简单Javascript Object ,用于定义粒子的行为。 并且所有粒子都将存储在Array ,在我们的代码中是particles

Then, adding a new particle to our system is as simple as creating a new Javascrit Object and adding it to the particles array. Check the comments so you understand the purpose of each property:

然后,将新粒子添加到我们的系统就像创建一个新的Javascrit Object并将其添加到particles数组一样简单。 检查注释,以便您了解每个属性的用途:

// 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
    });
}

Now that we have defined the basic structure of our particle system, we need a loop function, which allows us to add new particles, update them and draw them on the canvas in each animation frame. Something simple like this:

现在,我们已经定义了粒子系统的基本结构,我们需要一个循环函数,该函数允许我们添加新的粒子,更新它们并将其绘制在每个动画帧中的canvas上。 像这样简单的东西:

// 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
}

Now let's see how we have defined all the functions that we call inside the loop. As always, pay attention to the comments:

现在,让我们看看如何定义在循环中调用的所有函数。 与往常一样,请注意以下注释:

// 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);
    }
}

And we have our simple particle system ready, where we can add new particles defining the options we want, and the loop will be responsible for performing the animation.

我们已经准备好了简单的粒子系统,可以在其中添加新的粒子来定义所需的选项,而循环将负责执行动画。

为上传图标添加动画 ( Adding animations for the upload icon )

Now let's see how we prepare the upload icon to be animated:

现在,让我们看一下如何准备要上载动画的上载图标:

// 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();

With the previous code, we only need a couple of other functions to pause or resume the animation of the upload icon, as appropriate:

使用前面的代码,我们仅需要适当的几个其他函数即可暂停或恢复上传图标的动画:

// 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);
    }
}

添加拖放功能 ( Adding the drag and drop functionality )

Then we can start adding the drag and drop functionality to upload the files. Let's start by preventing unwanted behaviors for each related event:

然后我们就可以开始添加dragdrop功能,上传的文件。 让我们从防止每个相关事件的不良行为开始:

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

Now we will handle the events of type drag, where we will activate the form so that it is shown, and we will play the animations for the upload icon:

现在,我们将处理类型为drag的事件,在该事件中,我们将激活form以使其显示,并为上载图标播放动画:

// 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();
        }
    });
});

In case the user leaves the drop zone, we simply hide the form again and pause the animations for the upload icon:

如果用户离开drop区,我们只需再次隐藏form并暂停上载图标的动画即可:

// 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();
        }
    });
});

And finally the most important event that we must handle is the drop event, because it will be where we will obtain the files that the user has dropped, we will execute the corresponding animations, and if this were a fully functional component we would upload the files to the server through AJAX.

最后,我们必须处理的最重要的事件是drop事件,因为它将是获取用户放置的文件的位置,我们将执行相应的动画,如果这是功能齐全的组件,我们将上载文件通过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();
        }
    }
});

In the previous code snippet we saw that the function addParticlesOnDrop is called, which is in charge of executing the particle animation from where the files were dropped. Let's see how we can implement this function:

在前面的代码片段中,我们看到调用了addParticlesOnDrop函数,该函数负责执行放置文件的粒子动画。 让我们看看如何实现此功能:

// 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
    });
}

Finally, when the particles reach the position of the icon, we must move the icon upwards, giving the impression that the files are being uploaded:

最后,当粒子到达图标的位置时,我们必须向上移动图标,给人的印象是正在上传文件:

// 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);
        }
    });
}

To finish, we must implement the resetAll function, which resets the icon and all the variables to its initial state. We must also update the canvas size and reset the component on resize event. But in order not to make the tutorial longer and heavier, we have not included these and other minor details, although you can check the complete code in the Github repository.

最后,我们必须实现resetAll函数,该函数将图标和所有变量重置为其初始状态。 我们还必须更新canvas大小,并在发生resize事件时重置组件。 但是为了不使本教程变得更长而又繁重,尽管您可以在Github存储库中检查完整的代码,但我们并未包含这些细节和其他次要细节。

结论 ( Conclusion )

And finally our component is complete! Let's take a look:

最后,我们的组件完成了! 让我们来看看:

Creative Upload Interaction

You can check the live demo, play with the code on Codepen, or get the full code on Github.

您可以查看现场演示在Codepen上使用代码 ,或在Github上获取完整代码

Throughout the tutorial we saw how to create a simple particle system, as well as handle drag and drop events to implement an eye-catching file upload component.

在本教程中,我们看到了如何创建一个简单的粒子系统,以及手柄dragdrop事件来实现一个醒目的文件上传组件。

Remember that this component is not ready to be used in production. In case you want to complete the implementation to make it fully functional, I recommend checking this excellent tutorial in CSS Tricks.

请记住,该组件尚未准备好在生产中使用。 如果您想完成实现以使其完全发挥作用,我建议您在CSS Tricks中检查该出色的教程

We hope you liked the final result, and that this tutorial has been useful!

我们希望您喜欢最终结果,并且本教程对您有所帮助!

翻译自: https://scotch.io/tutorials/developing-a-creative-upload-interaction-with-javascript-and-canvas

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值