three.js使用svg_使用SVG和anime.js构建弹性范围输入

本文介绍如何使用SVG和anime.js构建一个模仿HTML5 的行为的弹性范围输入组件,通过SVG绘制图形,anime.js实现动画效果,提供一种更美观的表单元素替代方案。
摘要由CSDN通过智能技术生成

three.js使用svg

Among all the fascinating web components we can found in any UI, forms are usually the most boring part. In the past, there used to be only a few text input elements, where the user had to enter the data manually. Then with HTML5 things improved a lot, since new types of input appeared, such as color, date and range, among many others.

在任何用户界面中都能找到的所有迷人的Web组件中,表单通常是最无聊的部分。 过去,过去只有几个文本input元素,用户必须手动输入数据。 然后使用HTML5进行了很多改进,因为出现了新的input类型,例如colordaterange等等。

Although functionally these new types of input works, we can say that they often do not meet the aesthetic needs of web applications, then many proposals have emerged to replace these elements and achieve a better appearance, and looking almost identical in all modern browsers.

尽管这些新类型的input功能上是可行的,但可以说它们通常不能满足Web应用程序的审美需求,但随后出现了许多建议来替换这些元素并获得更好的外观,并且在所有现代浏览器中看起来几乎相同。

In this tutorial we will see how we can simulate the behavior of a range input, with an elegant component like this:

在本教程中,我们将看到如何使用如下所示的优雅组件来模拟range input的行为:

Dribble Shot by Stan Yakusevich

This original animation, which we have used as inspiration, can be found on this dribble shot by Stan Yakusevich.

斯坦·雅库塞维奇(Stan Yakusevich)的这一运球射门中可以找到我们用作灵感的原始动画。

To code it, we will mainly use SVG to draw the paths, and anime.js to perform the animations.

要进行编码,我们将主要使用SVG绘制path s,并使用anime.js进行动画处理。

Above is the final product we'll make. Let's start!

以上是我们要做的最终产品。 开始吧!

标记编码:HTML和SVG ( Coding the Markup: HTML and SVG )

Next we will see the main HTML structure that we will use. Please read the comments so you do not miss a single detail:

接下来,我们将看到我们将使用的主要HTML结构。 请阅读评论,以免您错过任何一个细节:

<!-- Wrapper for the range input slider -->
< div class = " range__wrapper " >
    <!-- The real input, it will be hidden, but updated properly with Javascript -->
    <!-- For a production usage, you may want to add a label and also put it inside a form -->
    < input class = " range__input " type = " range " min = " 30 " max = " 70 " value = " 64 " />

    <!-- All the other elements will go here -->
</ div >

As we can see, our component contains an actual input of type range, which we will update properly with Javascript. Having this input element and our component into a common HTML form, allows us to send the value of the input (along with the other form data) to server on submit.

如我们所见,我们的组件包含类型为range的实际input ,我们将使用Javascript对其进行正确更新。 将此input元素和我们的组件转换为常见HTML表单,使我们可以在submit上将input的值(以及其他表单数据)发送到服务器。

Now let's see the SVG elements that we need, commented for a better understanding:

现在,让我们看一下需要的SVG元素,并对其进行了注释,以更好地理解:

<!-- SVG elements -->
< svg class = " range__slider " width = " 320px " height = " 480px " viewBox = " 0 0 320 480 " >
    < defs >
        <!-- Range marks symbol, it will be reused below -->
        < symbol id = " range__marks " shape-rendering = " crispEdges " >
            < path class = " range__marks__path " d = " M 257 30 l 33 0 " > </ path >
            < path class = " range__marks__path " d = " M 268 60 l 22 0 " > </ path >
            < path class = " range__marks__path " d = " M 278 90 l 12 0 " > </ path >
            < path class = " range__marks__path " d = " M 278 120 l 12 0 " > </ path >
            < path class = " range__marks__path " d = " M 278 150 l 12 0 " > </ path >
            < path class = " range__marks__path " d = " M 278 180 l 12 0 " > </ path >
            < path class = " range__marks__path " d = " M 278 210 l 12 0 " > </ path >
            < path class = " range__marks__path " d = " M 278 240 l 12 0 " > </ path >
            < path class = " range__marks__path " d = " M 278 270 l 12 0 " > </ path >
            < path class = " range__marks__path " d = " M 278 300 l 12 0 " > </ path >
            < path class = " range__marks__path " d = " M 278 330 l 12 0 " > </ path >
            < path class = " range__marks__path " d = " M 278 360 l 12 0 " > </ path >
            < path class = " range__marks__path " d = " M 278 390 l 12 0 " > </ path >
            < path class = " range__marks__path " d = " M 268 420 l 22 0 " > </ path >
            < path class = " range__marks__path " d = " M 257 450 l 33 0 " > </ path >
        </ symbol >
        <!-- This clipPath element will allow us to hide/show the white marks properly -->
        <!-- The `path` used here is an exact copy of the `path` used for the slider below -->
        < clipPath id = " range__slider__clip-path " >
            < path class = " range__slider__path " d = " M 0 480 l 320 0 l 0 480 l -320 0 Z " > </ path >
        </ clipPath >
    </ defs >
    <!-- Pink marks -->
    < use xlink: href = " #range__marks " class = " range__marks__pink " > </ use >
    <!-- Slider `path`, that will be morphed properly on user interaction -->
    < path class = " range__slider__path " d = " M 0 480 l 320 0 l 0 480 l -320 0 Z " > </ path >
    <!-- Clipped white marks -->
    < use xlink: href = " #range__marks " class = " range__marks__white " clip-path = " url(#range__slider__clip-path) " > </ use >
</ svg >

If this is the first time you use the SVG path element or you don't understand how they work, you can learn more in this excellent tutorial in MDN.

如果这是您第一次使用SVG path元素,或者您不了解它们的工作原理,则可以在MDN的出色教程中了解更多信息。

And finally, we need another piece of code to show the values and texts that appears in the original animation:

最后,我们需要另一段代码来显示出现在原始动画中的值和文本:

<!-- Range values -->
< div class = " range__values " >
    < div class = " range__value range__value--top " >
        <!-- This element will be updated in the way: `100 - inputValue` -->
        < span class = " range__value__number range__value__number--top " > </ span >
        <!-- Some text for the `top` value -->
        < span class = " range__value__text range__value__text--top " >
            < span > Points </ span >
            < span > You Need </ span >
        </ span >
    </ div >
    < div class = " range__value range__value--bottom " >
        <!-- This element will be updated with the `inputValue` -->
        < span class = " range__value__number range__value__number--bottom " > </ span >
        <!-- Some text for the `bottom` value -->
        < span class = " range__value__text range__value__text--bottom " >
            < span > Points </ span >
            < span > You Have </ span >
        </ span >
    </ div >
</ div >

As you can see, the HTML code is quite easy to understand if we follow the comments. Now let's look at the styles.

如您所见,如果我们遵循注释,那么HTML代码非常容易理解。 现在让我们看看样式。

添加样式 ( Adding styles )

We will start styling the wrapper element:

我们将开始设计wrapper元素的样式:

.range__wrapper  {
  user-select : none ; // disable user selection, for better drag & drop

  // More code for basic styling and centering...
}

As you can see, apart from the basic styles to achieve a proper appearance and centering the element, we have disabled the user's ability to select anything within our component. This is very important, since we will implement a "drag and drop" type interaction, and therefore if we allow the "select" functionality, we can get unexpected behaviors.

如您所见,除了获得适当外观和使元素居中的基本样式外,我们还使用户无法选择组件中的任何内容。 这非常重要,因为我们将实现“拖放”类型的交互,因此,如果我们允许“选择”功能,则可能会出现意外行为。

Next we will hide the actual input element, and position the svg (.range__slider) element properly:

接下来,我们将隐藏实际的input元素,并正确.range__slider svg.range__slider )元素:

// Hide the `input`
.range__input  {
  display : none ;
}

// Position the SVG root element
.range__slider  {
  position : absolute ;
  left : 0 ;
  top : 0 ;
}

And to color the SVG elements we use the following code:

为了给SVG元素上色,我们使用以下代码:

// Slider color
.range__slider__path  {
  fill : #FF4B81 ;
}

// Styles for marks
.range__marks__path  {
  fill : none ;
  stroke : inherit ;
  stroke-width : 1 px ;
}

// Stroke color for the `pink` marks
.range__marks__pink  {
  stroke : #FF4B81 ;
}

// Stroke color for the `white` marks
.range__marks__white  {
  stroke : white ;
}

Now let's see the main styles used for the values. Here the transform-origin property plays an essential role to keep the numbers aligned with the text in the desired way, as in the original animation.

现在,让我们看一下用于值的主要样式。 在这里, transform-origin属性起着至关重要的作用,与原始动画一样,以所需的方式使数字与文本对齐。

// Positioning the container for values, it will be translated with Javascript
.range__values  {
  position : absolute ;
  left : 0 ;
  top : 0 ;
  width : 100% ;
}

// These `transform-origin` values will keep the numbers in the desired position as they are scaled
.range__value__number--top  {
  transform-origin : 100% 100% ; // bottom-right corner
}
.range__value__number--bottom  {
  transform-origin : 100% 0 ; // top-right corner
}

// More basic styles for the values...

添加与Javascript的交互 ( Adding interactions with Javascript )

Now it's time to add the interactions, start animating things and having fun :)

现在是时候添加交互​​,开始为事情添加动画和让他们开心了:)

First, let's see the code needed to simulate the drag and drop functionality, listening to corresponding events, doing maths work and perform animations. Please note we are not including the whole code, but only the fundamental parts to understand the behavior.

首先,让我们看一下模拟拖放功能,侦听相应事件,进行数学运算和执行动画所需的代码。 请注意,我们不包括完整的代码,而仅包括了解行为的基本部分。

// Handle `mousedown` and `touchstart` events, saving data about mouse position
function mouseDown ( e ) {
    mouseY = mouseInitialY = e . targetTouches ? e . targetTouches [ 0 ] . pageY : e . pageY ;
    rangeWrapperLeft = rangeWrapper . getBoundingClientRect ( ) . left ;
}

// Handle `mousemove` and `touchmove` events, calculating values to morph the slider `path` and translate values properly
function mouseMove ( e ) {
    if ( mouseY ) {
        // ... Some code for maths ...
        // After doing maths, update the value
        updateValue ( ) ;
    }
}

// Handle `mouseup`, `mouseleave` and `touchend` events
function mouseUp ( ) {
    // Trigger elastic animation in case `y` value has changed
    if ( mouseDy ) {
        elasticRelease ( ) ;
    }
    // Reset values
    mouseY = mouseDy = 0 ;
}

// Events listeners
rangeWrapper . addEventListener ( 'mousedown' , mouseDown ) ;
rangeWrapper . addEventListener ( 'touchstart' , mouseDown ) ;
rangeWrapper . addEventListener ( 'mousemove' , mouseMove ) ;
rangeWrapper . addEventListener ( 'touchmove' , mouseMove ) ;
rangeWrapper . addEventListener ( 'mouseup' , mouseUp ) ;
rangeWrapper . addEventListener ( 'mouseleave' , mouseUp ) ;
rangeWrapper . addEventListener ( 'touchend' , mouseUp ) ;

Now we can take a look at the updateValue function. This function is responsible for updating the component values and moving the slider in correspondence with the cursor position. We have commented exhaustively every part of it, for a better understanding:

现在我们来看看updateValue函数。 此功能负责更新组件值并根据光标位置移动滑块。 我们已经详尽地评论了其中的每个部分,以便更好地理解:

// Function to update the slider value
function updateValue ( ) {
    // Clear animations if are still running
    anime . remove ( [ rangeValues , rangeSliderPaths [ 0 ] , rangeSliderPaths [ 1 ] ] ) ;

    // Calc the `input` value using the current `y`
    rangeValue = parseInt ( currentY * max / rangeHeight ) ;
    // Calc `scale` value for numbers
    scale = ( rangeValue - rangeMin ) / ( rangeMax - rangeMin ) * scaleMax ;
    // Update `input` value
    rangeInput . value = rangeValue ;
    // Update numbers values
    rangeValueNumberTop . innerText = max - rangeValue ;
    rangeValueNumberBottom . innerText = rangeValue ;
    // Translate range values
    rangeValues . style . transform = 'translateY(' + ( rangeHeight - currentY ) + 'px)' ;
    // Apply corresponding `scale` to numbers
    rangeValueNumberTop . style . transform = 'scale(' + ( 1 - scale ) + ')' ;
    rangeValueNumberBottom . style . transform = 'scale(' + ( 1 - ( scaleMax - scale ) ) + ')' ;

    // Some maths calc
    if ( Math . abs ( mouseDy ) < mouseDyLimit ) {
        lastMouseDy = mouseDy ;
    } else {
        lastMouseDy = mouseDy < 0 ? - mouseDyLimit : mouseDyLimit ;
    }

    // Calc the `newSliderY` value to build the slider `path`
    newSliderY = currentY + lastMouseDy / mouseDyFactor ;
    if ( newSliderY < rangeMinY || newSliderY > rangeMaxY ) {
        newSliderY = newSliderY < rangeMinY ? rangeMinY : rangeMaxY ;
    }

    // Build `path` string and update `path` elements
    newPath = buildPath ( lastMouseDy , rangeHeight - newSliderY ) ;
    rangeSliderPaths [ 0 ] . setAttribute ( 'd' , newPath ) ;
    rangeSliderPaths [ 1 ] . setAttribute ( 'd' , newPath ) ;
}

As we have seen, within the previous function there is a call to the buildPath function, which is an essential piece in our component. This function will let us build the path for the slider, given the following parameters:

如我们所见,在先前的函数中,有一个对buildPath函数的调用,这是我们组件中必不可少的部分。 给定以下参数,此函数将让我们构建滑块的path

  • dy: distance in the y axis that mouse has been moved since the mousedown or touchstart event.

    dy :自mousedowntouchstart事件以来,鼠标已在y轴上移动的touchstart
  • ty: distance in the y axis that the path must be translated.

    tyy轴上必须平移path距离。

It also uses the mouseX value to draw the curve to the cursor position on the x axis, and return the path in String format:

它还使用mouseX值将曲线绘制到x轴上的光标位置,并以String格式返回path

// Function to build the slider `path`, using the given `dy` and `ty` values
function buildPath ( dy , ty ) {
    return 'M 0 ' + ty + ' q ' + mouseX + ' ' + dy + ' 320 0 l 0 480 l -320 0 Z' ;
}

Finally, let's see how to achieve the interesting elastic effect:

最后,让我们看看如何实现有趣的弹性效果:

// Function to simulate the elastic behavior
function elasticRelease ( ) {
    // Morph the paths to the opposite direction, to simulate a strong elasticity
    anime ( {
        targets : rangeSliderPaths ,
        d : buildPath ( - lastMouseDy * 1.3 , rangeHeight - ( currentY - lastMouseDy / mouseDyFactor ) ) ,
        duration : 150 ,
        easing : 'linear' ,
        complete : function ( ) {
            // Morph the paths to the normal state, using the `elasticOut` easing function (default)
            anime ( {
                targets : rangeSliderPaths ,
                d : buildPath ( 0 , rangeHeight - currentY ) ,
                duration : 4000 ,
                elasticity : 880
            } ) ;
        }
    } ) ;

    // Here will go a similar code to:
    // - Translate the values to the opposite direction, to simulate a strong elasticity
    // - Then, translate the values to the right position, using the `elasticOut` easing function (default)
}

As you can see, it was necessary to implement two consecutive animations to achieve an exaggerated elastic effect, similar to the original animation. This is because a single animation using the elasticOut easing function is not enough.

如您所见,类似于原始动画,有必要实现两个连续的动画以实现夸张的弹性效果。 这是因为使用elasticOut缓动功能的单个动画是不够的。

加起来 ( Summing up )

And finally we are done!

最后,我们完成了!

We have developed a component to simulate the behavior of an input of type range, but with an impressive effect, similar to the original animation:

我们开发了一个组件来模拟类型为rangeinput的行为,但具有令人印象深刻的效果,类似于原始动画:

Stan Yakusevich运球射门

You can check the final result, play with the code on Codepen, or get the full code on Github.

您可以检查最终结果在Codepen上使用代码 ,或在Github上获取完整代码

Please note that to make the tutorial a bit more fun and easy to follow, we have not explain here every single line of code used. However, you can find the complete code in the Github repository.

请注意,为了使本教程更加有趣和易于理解,我们在这里没有解释每行使用的代码。 但是,您可以在Github存储库中找到完整的代码。

We sincerely hope that you liked the tutorial, and it has served as inspiration!

我们衷心希望您喜欢该教程,它对我们有启发!

翻译自: https://scotch.io/tutorials/build-an-elastic-range-input-with-svg-and-animejs

three.js使用svg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值