简介
在HTML5中,为form
元素引入了许多新类型的input
属性,例如Color
、Date
、range
等等。虽然从功能上讲,这些新类型的输入
作品并不能满足网络应用程序的审美需求。
为了给这些input
类型一个现代的设计,可以使用这样的正面,本教程将使用一个组件来模拟range``input
的行为,使用svg绘制路径
和Anime.js来执行动画。
学习本教程后,您将了解创建如下range``input
设计的基本步骤:
设计的范围输入
<$>[备注] 注: 这是我们用来作为灵感的原创动画,可以在斯坦·雅库塞维奇的This Drobble shotby Stan Yakusevich]上找到。 <$>
如果您想看到最终产品,请在CodePen.上查看
第一步-使用Html和SVG编码标记
在第一步中,我们将看到我们将使用的主要HTML结构。请阅读评论,这样你就不会错过任何细节:
1<!-- Wrapper for the range input slider -->
2<div class="range__wrapper">
3 <!-- The real input will be hidden, but updated properly with Javascript -->
4 <!-- For a production usage, you may want to add a label and also put it inside a form -->
5 <input class="range__input" type="range" min="30" max="70" value="64"/>
6
7 <!-- All the other elements will go here -->
8</div>
正如我们所看到的,我们的组件包含一个实际的input
,类型为range
,我们将使用Java脚本正确地更新它。将这个input
元素和我们的组件放在一个通用的HTML表单中允许我们将input
的值(以及其他表单数据)发送到submit
上的服务器。
现在让我们来看看我们需要的SVG元素,为了更好地理解,我们进行了注释:
1<!-- SVG elements -->
2<svg class="range__slider" width="320px" height="480px" viewBox="0 0 320 480">
3 <defs>
4 <!-- Range marks symbol. It will be reused below -->
5 <symbol id="range__marks" shape-rendering="crispEdges">
6 <path class="range__marks__path" d="M 257 30 l 33 0"></path>
7 <path class="range__marks__path" d="M 268 60 l 22 0"></path>
8 <path class="range__marks__path" d="M 278 90 l 12 0"></path>
9 <path class="range__marks__path" d="M 278 120 l 12 0"></path>
10 <path class="range__marks__path" d="M 278 150 l 12 0"></path>
11 <path class="range__marks__path" d="M 278 180 l 12 0"></path>
12 <path class="range__marks__path" d="M 278 210 l 12 0"></path>
13 <path class="range__marks__path" d="M 278 240 l 12 0"></path>
14 <path class="range__marks__path" d="M 278 270 l 12 0"></path>
15 <path class="range__marks__path" d="M 278 300 l 12 0"></path>
16 <path class="range__marks__path" d="M 278 330 l 12 0"></path>
17 <path class="range__marks__path" d="M 278 360 l 12 0"></path>
18 <path class="range__marks__path" d="M 278 390 l 12 0"></path>
19 <path class="range__marks__path" d="M 268 420 l 22 0"></path>
20 <path class="range__marks__path" d="M 257 450 l 33 0"></path>
21 </symbol>
22 <!-- This clipPath element will allow us to hide/show the white marks properly -->
23 <!-- The `path` used here is an exact copy of the `path` used for the slider below -->
24 <clipPath id="range__slider__clip-path">
25 <path class="range__slider__path" d="M 0 480 l 320 0 l 0 480 l -320 0 Z"></path>
26 </clipPath>
27 </defs>
28 <!-- Pink marks -->
29 <use xlink:href="#range__marks" class="range__marks__pink"></use>
30 <!-- Slider `path`, that will be morphed properly on user interaction -->
31 <path class="range__slider__path" d="M 0 480 l 320 0 l 0 480 l -320 0 Z"></path>
32 <!-- Clipped white marks -->
33 <use xlink:href="#range__marks" class="range__marks__white" clip-path="url(#range__slider__clip-path)"></use>
34</svg>
<$>[备注]
注意: 如果这是您第一次使用svgpath
元素,或者您不了解它们的工作原理,您可以在Mozilla.的本教程》中了解更多信息
<$>
最后,我们需要另一段代码来显示原始动画中出现的值和文本:
1<!-- Range values -->
2<div class="range__values">
3 <div class="range__value range__value--top">
4 <!-- This element will be updated in the way: `100 - inputValue` -->
5 <span class="range__value__number range__value__number--top"></span>
6 <!-- Some text for the `top` value -->
7 <span class="range__value__text range__value__text--top">
8 <span>Points</span>
9 <span>You Need</span>
10 </span>
11 </div>
12 <div class="range__value range__value--bottom">
13 <!-- This element will be updated with the `inputValue` -->
14 <span class="range__value__number range__value__number--bottom"></span>
15 <!-- Some text for the `bottom` value -->
16 <span class="range__value__text range__value__text--bottom">
17 <span>Points</span>
18 <span>You Have</span>
19 </span>
20 </div>
21</div>
现在让我们来看看这些款式。
第二步-添加样式
我们将开始设计wrapper
元素的样式:
1.range__wrapper {
2 user-select: none; // disable user selection, for better drag & drop
3
4 // More code for basic styling and centering...
5}
正如您所看到的,除了实现适当外观和居中元素的基本样式外,我们还禁用了用户在组件中选择任何内容的功能。这一点很重要,因为我们将实现拖放
类型的交互,因此,如果我们允许选择
功能,我们可能会获得意外的行为。
接下来我们将隐藏实际的input
元素,并正确定位svg
(.range__slider
)元素:
1// Hide the `input`
2.range__input {
3 display: none;
4}
5
6// Position the SVG root element
7.range__slider {
8 position: absolute;
9 left: 0;
10 top: 0;
11}
为了给SVG元素上色,我们使用以下代码:
1// Slider color
2.range__slider__path {
3 fill: #FF4B81;
4}
5
6// Styles for marks
7.range__marks__path {
8 fill: none;
9 stroke: inherit;
10 stroke-width: 1px;
11}
12
13// Stroke color for the `pink` marks
14.range__marks__pink {
15 stroke: #FF4B81;
16}
17
18// Stroke color for the `white` marks
19.range__marks__white {
20 stroke: white;
21}
现在让我们看看用于值的主要样式。在这里,transform-origin
属性在保持数字与文本以所需方式对齐方面起着重要作用,就像在原始动画中一样。
1// Positioning the container for values; it will be translated with Javascript
2.range__values {
3 position: absolute;
4 left: 0;
5 top: 0;
6 width: 100%;
7}
8
9// These `transform-origin` values will keep the numbers in the desired position as they are scaled
10.range__value__number--top {
11 transform-origin: 100% 100%; // bottom-right corner
12}
13.range__value__number--bottom {
14 transform-origin: 100% 0; // top-right corner
15}
16
17// More basic styles for the values...
第三步-添加与JavaScript的交互
现在是添加交互并开始制作动画的时候了。
首先,让我们看看模拟拖放功能、监听相应事件、执行数学运算和执行动画所需的代码。请注意,我们没有包括整个代码,而只是理解行为的基本部分。
1// Handle `mousedown` and `touchstart` events, saving data about mouse position
2function mouseDown(e) {
3 mouseY = mouseInitialY = e.targetTouches ? e.targetTouches[0].pageY : e.pageY;
4 rangeWrapperLeft = rangeWrapper.getBoundingClientRect().left;
5}
6
7// Handle `mousemove` and `touchmove` events, calculating values to morph the slider `path` and translate values properly
8function mouseMove(e) {
9 if (mouseY) {
10 // ... Some code for maths ...
11 // After doing maths, update the value
12 updateValue();
13 }
14}
15
16// Handle `mouseup`, `mouseleave`, and `touchend` events
17function mouseUp() {
18 // Trigger elastic animation in case `y` value has changed
19 if (mouseDy) {
20 elasticRelease();
21 }
22 // Reset values
23 mouseY = mouseDy = 0;
24}
25
26// Events listeners
27rangeWrapper.addEventListener('mousedown', mouseDown);
28rangeWrapper.addEventListener('touchstart', mouseDown);
29rangeWrapper.addEventListener('mousemove', mouseMove);
30rangeWrapper.addEventListener('touchmove', mouseMove);
31rangeWrapper.addEventListener('mouseup', mouseUp);
32rangeWrapper.addEventListener('mouseleave', mouseUp);
33rangeWrapper.addEventListener('touchend', mouseUp);
现在我们来看一下updateValue
函数。此功能负责更新组件值并根据光标位置移动滑块。为了更好地理解,我们对其中的每一个部分都进行了详尽的评论:
1// Function to update the slider value
2function updateValue() {
3 // Clear animations if are still running
4 anime.remove([rangeValues, rangeSliderPaths[0], rangeSliderPaths[1]]);
5
6 // Calculate the `input` value using the current `y`
7 rangeValue = parseInt(currentY * max / rangeHeight);
8 // Calculate `scale` value for numbers
9 scale = (rangeValue - rangeMin) / (rangeMax - rangeMin) * scaleMax;
10 // Update `input` value
11 rangeInput.value = rangeValue;
12 // Update numbers values
13 rangeValueNumberTop.innerText = max - rangeValue;
14 rangeValueNumberBottom.innerText = rangeValue;
15 // Translate range values
16 rangeValues.style.transform = 'translateY(' + (rangeHeight - currentY) + 'px)';
17 // Apply corresponding `scale` to numbers
18 rangeValueNumberTop.style.transform = 'scale(' + (1 - scale) + ')';
19 rangeValueNumberBottom.style.transform = 'scale(' + (1 - (scaleMax - scale)) + ')';
20
21 // Some math calculations
22 if (Math.abs(mouseDy) < mouseDyLimit) {
23 lastMouseDy = mouseDy;
24 } else {
25 lastMouseDy = mouseDy < 0 ? -mouseDyLimit : mouseDyLimit;
26 }
27
28 // Calculate the `newSliderY` value to build the slider `path`
29 newSliderY = currentY + lastMouseDy / mouseDyFactor;
30 if (newSliderY < rangeMinY || newSliderY > rangeMaxY) {
31 newSliderY = newSliderY < rangeMinY ? rangeMinY : rangeMaxY;
32 }
33
34 // Build `path` string and update `path` elements
35 newPath = buildPath(lastMouseDy, rangeHeight - newSliderY);
36 rangeSliderPaths[0].setAttribute('d', newPath);
37 rangeSliderPaths[1].setAttribute('d', newPath);
38}
正如我们已经看到的,在前面的函数中有一个对BuildPath‘函数的调用,该函数是我们的组件中的一个基本部分。此函数将允许我们构建滑块的
路径`,给定以下参数:
dy
:自Mousedown
或Touchstart
事件以来,鼠标在`y‘轴上移动的距离。ty
:路径
必须平移的y
轴距离。
它还使用MouseX
值将曲线绘制到x
轴上的光标位置,并以String
格式返回path
:
1// Function to build the slider `path`, using the given `dy` and `ty` values
2function buildPath(dy, ty) {
3 return 'M 0 ' + ty + ' q ' + mouseX + ' ' + dy + ' 320 0 l 0 480 l -320 0 Z';
4}
最后,让我们来看看如何实现有趣的弹性效果:
1// Function to simulate the elastic behavior
2function elasticRelease() {
3 // Morph the paths to the opposite direction, to simulate a strong elasticity
4 anime({
5 targets: rangeSliderPaths,
6 d: buildPath(-lastMouseDy * 1.3, rangeHeight - (currentY - lastMouseDy / mouseDyFactor)),
7 duration: 150,
8 easing: 'linear',
9 complete: function () {
10 // Morph the paths to the normal state, using the `elasticOut` easing function (default)
11 anime({
12 targets: rangeSliderPaths,
13 d: buildPath(0, rangeHeight - currentY),
14 duration: 4000,
15 elasticity: 880
16 });
17 }
18 });
19
20 // Here will go a similar code to:
21 // - Translate the values to the opposite direction, to simulate a strong elasticity
22 // - Then, translate the values to the right position, using the `elasticOut` easing function (default)
23}
正如您所看到的,需要实现两个连续的动画来实现夸张的弹性效果,类似于原始动画。这是因为使用elasticOut
缓动函数的单个动画是不够的。
结论
在本教程中,我们开发了一个组件来模拟range
类型的input
的行为,但效果令人印象深刻,类似于原始动画:
运球