动画仪表未正确更新

时间:2018-03-21 22:16:24

标签: javascript css transition

我创建了这个动画仪表,其中绿色填充和白色数字转换为数字字段中新输入的值。有时它工作正常,但大多数情况下,仪表数字只会接近字段值或根本不会改变。例如,如果输入400,000,数字将匹配,但如果输入450,000,则数字将停在442,500。

broken meter

updateGauge()中存在问题,因为它在转换的任何部分都获得了填充的宽度。我搞砸了一段时间却没有运气。帮助将不胜感激!

CodePen demo



window.requestAnimFrame = (function(callback) {
        return window.requestAnimationFrame || 
			window.webkitRequestAnimationFrame || 
			window.mozRequestAnimationFrame || 
			window.oRequestAnimationFrame || 
			window.msRequestAnimationFrame ||
        function(callback) {
          window.setTimeout(callback, 1000 / 60);
        };
})();

document.addEventListener("DOMContentLoaded", app);

function app() {
	let inputVal = document.getElementById("val"),
		gaugeVal = document.querySelector(".gauge-value"),
		gauge = document.querySelector(".gauge-inner"),
		gaugeFill = document.querySelector(".gauge-fill"),
		
		updateGauge = function() {
			let rawDigits = (gaugeFill.offsetWidth / gauge.offsetWidth) * inputVal.max,
				roundedDigits = Math.round(rawDigits),
				digits = String(roundedDigits).split("").reverse(),
				displayValueArr = [],
				displayValue = "";

			// use reversed digits to make comma insertion easier
			for (let d in digits) {
				displayValueArr.unshift(digits[d]);
				if (d % 3 == 2 && d < digits.length - 1) {
					displayValueArr.unshift(",");
				}
			}
			for (let a in displayValueArr) {
				displayValue += displayValueArr[a];
			}

			gaugeVal.innerHTML = displayValue;
			
			// update until value reached
			if (rawDigits != inputVal.value) {
				requestAnimFrame(updateGauge);
			}
		},
		updateVal = function() {
			// keep input in range
			if (+inputVal.value > inputVal.max) {
				inputVal.value = inputVal.max;
				
			} else if (+inputVal.value < inputVal.min) {
				inputVal.value = inputVal.min;
			}
			
			gaugeFill.style.width = (inputVal.value/inputVal.max * 100) + "%";
			updateGauge();
		},
		userEvent = "oninput" in document.documentElement ? "input": "change";
	
	inputVal.addEventListener(userEvent,updateVal);
}
&#13;
@import "https://fonts.googleapis.com/css?family=Hind:400,700";

*, *:before, *:after {
	box-sizing: border-box;
	margin: 0;
	padding: 0;
}
:root {
	font-size: 20px;
}
body, input {
	background: #aca;
	font: 1em "Hind", Helvetica, sans-serif;
	line-height: 1.5;
}
input {
	background: #fff;
	border: 0;
	box-shadow: 0 0 0 1px #aaa inset;
	-webkit-appearance: none;
	-moz-appearance: none;
	appearance: none;
	padding: 0.375em;
}
label {
	display: block;
}
main {
	padding: 3em 1.5em;
}
form, .gauge {
	margin: auto;
}
form {
	text-align: center;
}
.gauge {
	font-size: 2em;
	margin-bottom: 0.75em;
	outline: 0;
	position: relative;
	width: 10em;
	height: 1.5em;
	transition: all 0.1s linear;
}
.gauge:hover {
	filter: brightness(1.1);
	-webkit-filter: brightness(1.1);
}
.gauge:active {
	filter: brightness(1.25);
	-webkit-filter: brightness(1.25);
	transform: scale(0.95,0.95);
}
.gauge:not(:active):focus > .gauge-details {
	visibility: visible;
}
.gauge > *, .gauge-inner:before, .gauge-details:before {
	position: absolute;
}
/* Statistical */
.gauge-stats {
	top: 0;
	transform: translateY(-25%);
	padding: 0 0.375em;
	z-index: 1;
}
.gauge-symbol, .gauge-value {
	font-weight: bold;
	-webkit-text-stroke: 2px #000;
	vertical-align: middle;
	text-shadow: 0 0.1em 0 #000;
}
.gauge-symbol {
	color: #4c4;
	font-size: 2em;
	line-height: 1.5;
}
.gauge-value {
	color: #fff;
}
/* Fill */
.gauge-inner, .gauge-fill {
	height: 100%;
}
.gauge-inner {
	border: 0.075em solid black;
	border-radius: 0.375em;
	overflow: hidden;
	width: 100%;
}
.gauge-inner:before {
	background: #fff;
	border-radius: 0.375em;
	content: "";
	display: block;
	opacity: 0.5;
	top: 0.125em;
	left: 0.125em;
	width: calc(100% - 0.25em);
	height: 0.5em;
}
.gauge-fill {
	background: linear-gradient(-90deg,rgba(255,255,255,0.7),rgba(255,255,255,0) 0.2em) #080;
	transition: width 1s linear;
	width: 50%;
}
/* Popover */
.gauge-details {
	background: rgba(0,0,0,0.7);
	border-radius: 0.25em;
	color: #fff;
	visibility: hidden;
	font-size: 0.5em;
	padding: 0.375em 0.75em;
	text-align: right;
	text-shadow: 0 0.1em 0 #000;
	top: calc(100% + 0.75em);
	left: 50%;
	transform: translateX(-50%);
}
.gauge-details:before {
	border-left: 0.5em solid transparent;
	border-right: 0.5em solid transparent;
	border-bottom: 0.5em solid rgba(0,0,0,0.7);
	content: "";
	width: 0;
	height: 0;
	bottom: 100%;
	left: calc(50% - 0.5em);
}
&#13;
<main>
	<div class="gauge" tabindex="0">
		<div class="gauge-stats">
			<span class="gauge-symbol">$</span>
			<span class="gauge-value">500,000</span>
		</div>
		<div class="gauge-inner">
			<div class="gauge-fill"></div>
		</div>
		<div class="gauge-details">Max: <strong>1,000,000</strong></div>
	</div>
	<form>
		<label for="val">Change value to:</label>
		<input id="val" type="number" min="0" step="1" max="1000000" value="500000">
	</form>
</main>
&#13;
&#13;
&#13;

1 个答案:

答案 0 :(得分:1)

问题与这个比例有关:

(gaugeFill.offsetWidth / gauge.offsetWidth)

offsetWidth返回元素的宽度,包括其边框。 所以你可以通过删除类gauge-inner的边框来摆脱你的问题。 如果你不太贴在你的圆形边框上,你可以用一个简单的直线轮廓替换它。 否则,您可以使用box-shadow创建看起来像边框的东西。

另外,因为在你的情况下,gaugeFill.offsetWidth是一个只能低于或等于400的整数,所以roundedDigits只有400个可能的值,所以它不是很准确。 这就是为什么你应该用inputVal.value替换显示的数字。(我自己没有这样做,因为你可以看到仪表被修复了。)

    window.requestAnimFrame = (function(callback) {
        return window.requestAnimationFrame || 
			window.webkitRequestAnimationFrame || 
			window.mozRequestAnimationFrame || 
			window.oRequestAnimationFrame || 
			window.msRequestAnimationFrame ||
        function(callback) {
          window.setTimeout(callback, 1000 / 60);
        };
})();

document.addEventListener("DOMContentLoaded", app);

function app() {
	let inputVal = document.getElementById("val"),
		gaugeVal = document.querySelector(".gauge-value"),
		gauge = document.querySelector(".gauge-inner"),
		gaugeFill = document.querySelector(".gauge-fill"),
		
        // Added variables
        oldInputValue = 500000,
        timeDifference = 0,
        valueDifference = 0,
        timeBetweenUpdating = 100; // (in milliseconds) The digits will be updated every 0.1s
        
		updateGauge = function() {
            
			/*let rawDigits = ((gaugeFill.offsetWidth) / (gauge.offsetWidth)) * inputVal.max,
				roundedDigits = Math.round(rawDigits),*/
            
            // the value of the digits when they change rapidly
            var changingDigits = oldInputValue;
            // the time left to animate digits
            var timeLeft = Math.abs(timeDifference);
            
            oldInputValue = Number(inputVal.value);
            
            // Every 0.1s the digits a recalculated and displayed.        
            var digitsUpdating = setInterval(function () {
                changingDigits += (timeBetweenUpdating/timeDifference)*valueDifference;

                timeLeft -=timeBetweenUpdating;

                // If there is no time left (the animation is over)
                // the updating stops and the last value to be displayed is inputVal.value
                if (timeLeft < 0)
                {
                    changingDigits = inputVal.value;
                    clearInterval(digitsUpdating);
                }

                digits = String(Math.trunc(changingDigits)).split("").reverse(),
                displayValueArr = [],
                displayValue = "";

                // use reversed digits to make comma insertion easier
                for (let d in digits) {
                    displayValueArr.unshift(digits[d]);
                    if (d % 3 == 2 && d < digits.length - 1) {
                        displayValueArr.unshift(",");
                    }
                }
                for (let a in displayValueArr) {
                    displayValue += displayValueArr[a];
                }

                gaugeVal.innerHTML = displayValue;

            }, timeBetweenUpdating);

			
			// update until value reached
			/*if (rawDigits != inputVal.value) {
				requestAnimFrame(updateGauge);
			}*/
		},
		updateVal = function() {
			// keep input in range
			if (+inputVal.value > inputVal.max) {
				inputVal.value = inputVal.max;
				
			} else if (+inputVal.value < inputVal.min) {
				inputVal.value = inputVal.min;
			}
            
            // Added code; animate the gauge fill.
            valueDifference = inputVal.value - oldInputValue;
            var width = String((inputVal.value/ inputVal.max) * 100) + "%";
            timeDifference = (Math.abs(valueDifference) / inputVal.max) * 1500; // Fill the gauge entirely would take 1500ms.
            jQuery(gaugeFill).animate({width: width}, timeDifference);

			/*gaugeFill.style.width = (inputVal.value/inputVal.max * 100) + "%";*/
			updateGauge();
		},
		userEvent = "oninput" in document.documentElement ? "input": "change";

	inputVal.addEventListener(userEvent,updateVal);
}
@import "https://fonts.googleapis.com/css?family=Hind:400,700";

*, *:before, *:after {
	box-sizing: border-box;
	margin: 0;
	padding: 0;
}
:root {
	font-size: 20px;
}
body, input {
	background: #aca;
	font: 1em "Hind", Helvetica, sans-serif;
	line-height: 1.5;
}
input {
	background: #fff;
	border: 0;
	box-shadow: 0 0 0 1px #aaa inset;
	-webkit-appearance: none;
	-moz-appearance: none;
	appearance: none;
	padding: 0.375em;
}
label {
	display: block;
}
main {
	padding: 3em 1.5em;
}
form, .gauge {
	margin: auto;
}
form {
	text-align: center;
}
.gauge {
	font-size: 2em;
	margin-bottom: 0.75em;
	outline: 0;
	position: relative;
	width: 10em;
	height: 1.5em;
	/*transition: all 0.1s linear;*/
}
.gauge:hover {
	filter: brightness(1.1);
	-webkit-filter: brightness(1.1);
}
.gauge:active {
	filter: brightness(1.25);
	-webkit-filter: brightness(1.25);
	transform: scale(0.95,0.95);
}
.gauge:not(:active):focus > .gauge-details {
	visibility: visible;
}
.gauge > *, .gauge-inner:before, .gauge-details:before {
	position: absolute;
}
/* Statistical */
.gauge-stats {
	top: 0;
	transform: translateY(-25%);
	padding: 0 0.375em;
	z-index: 1;
}
.gauge-symbol, .gauge-value {
	font-weight: bold;
	-webkit-text-stroke: 2px #000;
	vertical-align: middle;
	text-shadow: 0 0.1em 0 #000;
}
.gauge-symbol {
	color: #4c4;
	font-size: 2em;
	line-height: 1.5;
}
.gauge-value {
	color: #fff;
}
/* Fill */
.gauge-inner, .gauge-fill {
	height: 100%;
}
.gauge-inner {
	border: 0.075em solid black;
	border-radius: 0.375em;
	overflow: hidden;
	width: 100%;
}
.gauge-inner:before {
	background: #fff;
	border-radius: 0.375em;
	content: "";
	display: block;
	opacity: 0.5;
	top: 0.125em;
	left: 0.125em;
	width: calc(100% - 0.25em);
	height: 0.5em;
}
.gauge-fill {
	background: linear-gradient(-90deg,rgba(255,255,255,0.7),rgba(255,255,255,0) 0.2em) #080;
	/*transition: width 1s linear;*/
	width: 50%;
}
/* Popover */
.gauge-details {
	background: rgba(0,0,0,0.7);
	border-radius: 0.25em;
	color: #fff;
	visibility: hidden;
	font-size: 0.5em;
	padding: 0.375em 0.75em;
	text-align: right;
	text-shadow: 0 0.1em 0 #000;
	top: calc(100% + 0.75em);
	left: 50%;
	transform: translateX(-50%);
}
.gauge-details:before {
	border-left: 0.5em solid transparent;
	border-right: 0.5em solid transparent;
	border-bottom: 0.5em solid rgba(0,0,0,0.7);
	content: "";
	width: 0;
	height: 0;
	bottom: 100%;
	left: calc(50% - 0.5em);
}
<main>
	<div class="gauge" tabindex="0">
		<div class="gauge-stats">
			<span class="gauge-symbol">$</span>
			<span class="gauge-value">500,000</span>
		</div>
		
		

		<div class="gauge-inner">
			<div class="gauge-fill"></div>
		</div>

		
	
		
		<div class="gauge-details">Max: <strong>1,000,000</strong></div>
	</div>
	<form>
		<label for="val">Change value to:</label>
		<input id="val" type="number" min="0" step="1" max="1000000" value="500000">
	</form>
	
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</main>

第二次修改:

正如我之前所说,我使用jquery作为其animate()函数。所以我在HTML中添加了jQuery文件(最后)。我还删除了CSS中的transition属性。 由于我不再使用gaugeFill.offsetWidth,我重新建立了边界。

我在javascript文件中直接评论了我所做的更改。

动画似乎终于完美无缺。仪表填充和数字是动画的,边界重新建立。

我必须说这比我最初想的要困难得多......