同时使用 scaleX 和 translationX (VelocityJS)
Using scaleX and translationX at the same time (VelocityJS)
我有一个从某个百分比填充到另一个百分比 (0% - 50%) 的条。我想将它平滑地动画化到一个新的范围 (25% - 55%)。这意味着栏的宽度和位置都需要更改。我在同时顺利完成这两项操作时遇到了一些麻烦。
经过一些研究,我发现我需要使用 scaleX
来平滑地设置宽度动画,并使用 translateX
来平滑地设置位置动画(其中 translateX
也受比例的影响)。现在出现的问题是条形图将超出其所需的百分比 (55%),然后移回,如下面的代码片段所示。
/* Button function to restart. */
const restart = () => {
animate();
}
const animate = () => {
/* Reset the bar to its starting position. (from 0% - 50%) */
Velocity(document.getElementById('movingBar'), {
scaleX: 0.5,
translateX: 0
},
{
duration: 0,
easing: [0, 0, 1, 1]
});
/* Move the bar to its final position. (from 25% - 55%). */
/* Split into two velocity calls so that they can have a seperate duration/easing if needed. */
Velocity(document.getElementById('movingBar'), {
scaleX: 0.30
},
{
duration: 1000,
easing: [0, 0, 1, 1],
queue: false
});
Velocity(document.getElementById('movingBar'), {
translateX: (25 / 0.30) + '%'
},
{
duration: 1000,
easing: [0, 0, 1, 1],
queue: false
});
};
/* Start animation on run. */
animate();
#root {
width: 700px;
height: 100%;
}
#container {
width: 100%;
height: 90px;
background-color: #000000;
}
.bar {
width: 100%;
height: 30px;
transform: scaleX(0.5);
transform-origin: left;
background-color: #FF0000;
}
#description {
display: flex;
}
.percentage {
display: flex;
justify-content: flex-end;
width: 10%;
height: 20px;
text-align: right;
}
.odd {
background-color: #DDDDDD;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.2/velocity.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script>
<div id='root' style='width: 100%; height: 100%'>
<div id='container'>
<div class="bar" style="background-color: #00FF00;"></div>
<div id='movingBar' class="bar"></div>
<div class="bar" style="background-color: #00FF00; transform: scaleX(0.30) translateX(calc(25 / 0.30 * 1%))"></div>
</div>
<div id='description'>
<div class="percentage even">10%</div>
<div class="percentage odd">20%</div>
<div class="percentage even">30%</div>
<div class="percentage odd">40%</div>
<div class="percentage even">50%</div>
<div class="percentage odd">60%</div>
<div class="percentage even">70%</div>
<div class="percentage odd">80%</div>
<div class="percentage even">90%</div>
<div class="percentage odd">100%</div>
</div>
<button onClick="restart()">restart</button>
</div>
该示例在顶部绿色条中显示起始位置,在底部绿色条中显示动画应到达的所需位置。中间的红色条是应该从起始位置到所需位置的动画条。正如您所看到的,红色条最终达到了预期的结果,但没有超过 55%。我现在正在使用 VelocityJS 制作动画。
知道我做错了什么或者我将如何制作动画吗?我是否需要对 duration/easing 进行一些计算以纠正出现的问题?
问题与值的插值方式有关。确保使用 scaleX 和 translateX 进行全局线性变换将是一项 hard 的工作,因为您无法控制值的插值,而浏览器会为您执行此操作。所以要么你对 duration/easing 进行复杂的计算以找到完美的结果,要么你考虑另一种动画。
对于这种情况,我会考虑 clip-path
即使支持不是最佳的,但它会更容易处理,因为你只有一个 属性 来制作动画而且你不需要任何复杂的计算,因为您只需使用图表的百分比。
这是一个简化的例子:
body {
background:#000;
}
.box {
height:50px;
background:red;
clip-path:polygon(10% 100%,10% 0, 40% 0,40% 100%); /*from [10% 40%]*/
transition:1s all;
}
body:hover .box {
clip-path:polygon(50% 100%,50% 0, 60% 0,60% 100%); /*to [50% 60%]*/
}
<div class="box">
</div>
我在尝试寻找答案时从数学开始,并得到了这个几乎完美的翻译缓动:
[1/3, 0.2, 2/3, 1 - (8/15)];
我通过创建一个翻译路径应该是什么样子的公式来计算这些 25x / (-0.2x + 0.5)
。然后我将它除以 83.333333
以在 [0, 1] window 中得到所需的翻译公式。然后我使用 (25x / (-0.2x + 0.5)) / 83.333333
的公式来计算我在上面使用的立方贝塞尔点。请注意,我对第 4 点执行了 1-C2y,不知道为什么,否则它不会起作用。
注意:这适用于上面的原始问题,但当比例从 0.5 到 0.3 以外的任何其他值发生变化时,它就无法工作。我还在想为什么。
/* Button function to restart. */
const restart = () => {
animate();
}
const desiredX = 0.5;
let desiredYScale = 0;
let desiredYTranslation = 0;
const animate = () => {
const barMax = 100;
const scaleStart = 0.5;
const scaleEnd = 0.4;
const offsetStart = 0;
const offsetEnd = 10;
const translationStart = 100 / barMax * offsetStart / scaleStart;
const translationEnd = 100 / barMax * offsetEnd / scaleEnd;
const dataS = {};
const dataT = {};
const F = 0.5;
const scaleFormula = ((-scaleStart + scaleEnd) * F + scaleStart);
//console.log("scale formula: ", scaleFormula);
const translationPath = (offsetEnd * F) / scaleFormula;
const diffPath = translationPath - (offsetEnd / scaleEnd * F);
const diffFormulaA = - (diffPath / (F * F));
const diffFormula = (diffFormulaA/diffPath) * (F - 0.5) * (F - 0.5) + 1
const diffA = diffFormulaA / diffPath;
const cX = 0.5;
const cY = 0.5 * Math.abs(diffA);
const cAx = 2/3 * cX;
const cAy = 2/3 * cY;
const cBx = 2/3 * cX + 1/3 * 1;
const cBy = 2/3 * cY + 1/3 * 1;
const multiplicant = 0.5;
const realCAX = cAx / cBy * multiplicant;
const realCAY = cAy / cBy * multiplicant;
const realCBX = 1 - (cBx / cBy * multiplicant);
const realCBY = cBy / cBy * multiplicant;
console.log("realCAX: ", realCAX);
console.log("realCAY: ", realCAY);
console.log("realCBX: ", realCBX);
console.log("realCBY: ", realCBY);
const linearEasing = [0, 0, 1, 1];
//const one = 0.5 + (scaleEnd / 4);
//const two = 0.525 - (scaleStart - scaleEnd);
//const one = 0.40 + 0.025 / (0.5 - (scaleStart - scaleEnd));
//console.log("One: ", one, (scaleStart - scaleEnd));
//const one = 0.5;
//const one = 0.535;
//const one = 0.5;
const one = 0.5;
const two = 0.1;
//const two = 0.125;
//const translationEasing = [0.33, 10, 0.66, 16];
//const translationEasing = [1/3, 0.2, 2/3, 1-(8/15)];
//const translationEasing = [1/3, 1/15, 2/3, 1-0.4];
//const translationEasing = [0.24, 0.06666, 0.85, 0.1];
//const translationEasing = [0.33, 1.33, 0.66, 1.66];
//const translationEasing = [0.2, 0.8, 0.4, 1];
//const translationEasing = [0.1, 0.4, 1-0.2, 0.5];
//const translationEasing = [realCAX, realCAY, realCBX, realCBY];
//const translationEasing = [1/3, 0.0833333, 2/3, 0.42];
const translationEasing = [one, two, 1-two, 1-one];
//const translationEasing = [0, 0, 1, 1];5
/* Reset the bar to its starting position. (from 0% - 50%) */
Velocity(document.getElementById('movingBar'), {
scaleX: scaleStart,
translateX: translationStart + '%'
},
{
duration: 0,
easing: linearEasing
});
/* Move the bar to its final position. (from 25% - 55%). */
/* Split into two velocity calls so that they can have a seperate duration/easing if needed. */
Velocity(document.getElementById('movingBar'), {
scaleX: scaleEnd
},
{
duration: 1000,
easing: linearEasing,
queue: false,
progress: function(elements, complete, remaining, start, tweenValue) {
dataS[complete] = scaleStart + ((scaleEnd - scaleStart) * complete);
}
});
Velocity(document.getElementById('movingBar'), {
translateX: translationEnd + '%',
tween: translationEnd
},
{
duration: 1000,
easing: translationEasing,
queue: false,
progress: function(elements, complete, remaining, start, tweenValue) {
dataT[complete] = translationStart + ((translationEnd - translationStart) * complete);
console.log("TWEEN", complete, tweenValue);
},
complete: function() {
//console.log("DONE!");
//console.log("SCALE:");
//if (desiredX in dataS) {
//console.log('Scale[0.5]: ', dataS[desiredX], ', Translation[0.5]: ', dataT[desiredX]);
//desiredYScale = dataS[desiredX];
//desiredYTranslation = dataT[desiredX];
//} else {
//animate();
//}
for (const key in dataS) {
if (dataS.hasOwnProperty(key)) {
//console.log('', key, ': ', dataS[key]);
}
}
//console.log("TRANSLATION:");
for (const key in dataT) {
if (dataT.hasOwnProperty(key)) {
//console.log('', key, ': ', dataT[key]);
}
}
}
});
};
/* Start animation on run. */
animate();
#root {
width: 700px;
height: 100%;
}
#container {
width: 100%;
height: 90px;
background-color: #000000;
}
.bar {
width: 100%;
height: 30px;
transform: scaleX(0.5) transform (calc(20 / 0.5 * 1%));
transform-origin: left;
background-color: #FF0000;
}
#description {
display: flex;
}
.percentage {
display: flex;
justify-content: flex-end;
width: 10%;
height: 20px;
text-align: right;
}
.odd {
background-color: #DDDDDD;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.2/velocity.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script>
<div id='root' style='width: 100%; height: 100%'>
<div id='container'>
<div class="bar" style="background-color: #00FF00; transform: scaleX(0.5) translateX(calc(0 / 0.5 * 1%))"></div>
<div id='movingBar' class="bar"></div>
<div class="bar" style="background-color: #00FF00; transform: scaleX(0.4) translateX(calc(10 / 0.4 * 1%))"></div>
</div>
<div id='description'>
<div class="percentage even">10%</div>
<div class="percentage odd">20%</div>
<div class="percentage even">30%</div>
<div class="percentage odd">40%</div>
<div class="percentage even">50%</div>
<div class="percentage odd">60%</div>
<div class="percentage even">70%</div>
<div class="percentage odd">80%</div>
<div class="percentage even">90%</div>
<div class="percentage odd">100%</div>
</div>
<button onClick="restart()">restart</button>
</div>
我有一个从某个百分比填充到另一个百分比 (0% - 50%) 的条。我想将它平滑地动画化到一个新的范围 (25% - 55%)。这意味着栏的宽度和位置都需要更改。我在同时顺利完成这两项操作时遇到了一些麻烦。
经过一些研究,我发现我需要使用 scaleX
来平滑地设置宽度动画,并使用 translateX
来平滑地设置位置动画(其中 translateX
也受比例的影响)。现在出现的问题是条形图将超出其所需的百分比 (55%),然后移回,如下面的代码片段所示。
/* Button function to restart. */
const restart = () => {
animate();
}
const animate = () => {
/* Reset the bar to its starting position. (from 0% - 50%) */
Velocity(document.getElementById('movingBar'), {
scaleX: 0.5,
translateX: 0
},
{
duration: 0,
easing: [0, 0, 1, 1]
});
/* Move the bar to its final position. (from 25% - 55%). */
/* Split into two velocity calls so that they can have a seperate duration/easing if needed. */
Velocity(document.getElementById('movingBar'), {
scaleX: 0.30
},
{
duration: 1000,
easing: [0, 0, 1, 1],
queue: false
});
Velocity(document.getElementById('movingBar'), {
translateX: (25 / 0.30) + '%'
},
{
duration: 1000,
easing: [0, 0, 1, 1],
queue: false
});
};
/* Start animation on run. */
animate();
#root {
width: 700px;
height: 100%;
}
#container {
width: 100%;
height: 90px;
background-color: #000000;
}
.bar {
width: 100%;
height: 30px;
transform: scaleX(0.5);
transform-origin: left;
background-color: #FF0000;
}
#description {
display: flex;
}
.percentage {
display: flex;
justify-content: flex-end;
width: 10%;
height: 20px;
text-align: right;
}
.odd {
background-color: #DDDDDD;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.2/velocity.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script>
<div id='root' style='width: 100%; height: 100%'>
<div id='container'>
<div class="bar" style="background-color: #00FF00;"></div>
<div id='movingBar' class="bar"></div>
<div class="bar" style="background-color: #00FF00; transform: scaleX(0.30) translateX(calc(25 / 0.30 * 1%))"></div>
</div>
<div id='description'>
<div class="percentage even">10%</div>
<div class="percentage odd">20%</div>
<div class="percentage even">30%</div>
<div class="percentage odd">40%</div>
<div class="percentage even">50%</div>
<div class="percentage odd">60%</div>
<div class="percentage even">70%</div>
<div class="percentage odd">80%</div>
<div class="percentage even">90%</div>
<div class="percentage odd">100%</div>
</div>
<button onClick="restart()">restart</button>
</div>
该示例在顶部绿色条中显示起始位置,在底部绿色条中显示动画应到达的所需位置。中间的红色条是应该从起始位置到所需位置的动画条。正如您所看到的,红色条最终达到了预期的结果,但没有超过 55%。我现在正在使用 VelocityJS 制作动画。
知道我做错了什么或者我将如何制作动画吗?我是否需要对 duration/easing 进行一些计算以纠正出现的问题?
问题与值的插值方式有关。确保使用 scaleX 和 translateX 进行全局线性变换将是一项 hard 的工作,因为您无法控制值的插值,而浏览器会为您执行此操作。所以要么你对 duration/easing 进行复杂的计算以找到完美的结果,要么你考虑另一种动画。
对于这种情况,我会考虑 clip-path
即使支持不是最佳的,但它会更容易处理,因为你只有一个 属性 来制作动画而且你不需要任何复杂的计算,因为您只需使用图表的百分比。
这是一个简化的例子:
body {
background:#000;
}
.box {
height:50px;
background:red;
clip-path:polygon(10% 100%,10% 0, 40% 0,40% 100%); /*from [10% 40%]*/
transition:1s all;
}
body:hover .box {
clip-path:polygon(50% 100%,50% 0, 60% 0,60% 100%); /*to [50% 60%]*/
}
<div class="box">
</div>
我在尝试寻找答案时从数学开始,并得到了这个几乎完美的翻译缓动:
[1/3, 0.2, 2/3, 1 - (8/15)];
我通过创建一个翻译路径应该是什么样子的公式来计算这些 25x / (-0.2x + 0.5)
。然后我将它除以 83.333333
以在 [0, 1] window 中得到所需的翻译公式。然后我使用 (25x / (-0.2x + 0.5)) / 83.333333
的公式来计算我在上面使用的立方贝塞尔点。请注意,我对第 4 点执行了 1-C2y,不知道为什么,否则它不会起作用。
注意:这适用于上面的原始问题,但当比例从 0.5 到 0.3 以外的任何其他值发生变化时,它就无法工作。我还在想为什么。
/* Button function to restart. */
const restart = () => {
animate();
}
const desiredX = 0.5;
let desiredYScale = 0;
let desiredYTranslation = 0;
const animate = () => {
const barMax = 100;
const scaleStart = 0.5;
const scaleEnd = 0.4;
const offsetStart = 0;
const offsetEnd = 10;
const translationStart = 100 / barMax * offsetStart / scaleStart;
const translationEnd = 100 / barMax * offsetEnd / scaleEnd;
const dataS = {};
const dataT = {};
const F = 0.5;
const scaleFormula = ((-scaleStart + scaleEnd) * F + scaleStart);
//console.log("scale formula: ", scaleFormula);
const translationPath = (offsetEnd * F) / scaleFormula;
const diffPath = translationPath - (offsetEnd / scaleEnd * F);
const diffFormulaA = - (diffPath / (F * F));
const diffFormula = (diffFormulaA/diffPath) * (F - 0.5) * (F - 0.5) + 1
const diffA = diffFormulaA / diffPath;
const cX = 0.5;
const cY = 0.5 * Math.abs(diffA);
const cAx = 2/3 * cX;
const cAy = 2/3 * cY;
const cBx = 2/3 * cX + 1/3 * 1;
const cBy = 2/3 * cY + 1/3 * 1;
const multiplicant = 0.5;
const realCAX = cAx / cBy * multiplicant;
const realCAY = cAy / cBy * multiplicant;
const realCBX = 1 - (cBx / cBy * multiplicant);
const realCBY = cBy / cBy * multiplicant;
console.log("realCAX: ", realCAX);
console.log("realCAY: ", realCAY);
console.log("realCBX: ", realCBX);
console.log("realCBY: ", realCBY);
const linearEasing = [0, 0, 1, 1];
//const one = 0.5 + (scaleEnd / 4);
//const two = 0.525 - (scaleStart - scaleEnd);
//const one = 0.40 + 0.025 / (0.5 - (scaleStart - scaleEnd));
//console.log("One: ", one, (scaleStart - scaleEnd));
//const one = 0.5;
//const one = 0.535;
//const one = 0.5;
const one = 0.5;
const two = 0.1;
//const two = 0.125;
//const translationEasing = [0.33, 10, 0.66, 16];
//const translationEasing = [1/3, 0.2, 2/3, 1-(8/15)];
//const translationEasing = [1/3, 1/15, 2/3, 1-0.4];
//const translationEasing = [0.24, 0.06666, 0.85, 0.1];
//const translationEasing = [0.33, 1.33, 0.66, 1.66];
//const translationEasing = [0.2, 0.8, 0.4, 1];
//const translationEasing = [0.1, 0.4, 1-0.2, 0.5];
//const translationEasing = [realCAX, realCAY, realCBX, realCBY];
//const translationEasing = [1/3, 0.0833333, 2/3, 0.42];
const translationEasing = [one, two, 1-two, 1-one];
//const translationEasing = [0, 0, 1, 1];5
/* Reset the bar to its starting position. (from 0% - 50%) */
Velocity(document.getElementById('movingBar'), {
scaleX: scaleStart,
translateX: translationStart + '%'
},
{
duration: 0,
easing: linearEasing
});
/* Move the bar to its final position. (from 25% - 55%). */
/* Split into two velocity calls so that they can have a seperate duration/easing if needed. */
Velocity(document.getElementById('movingBar'), {
scaleX: scaleEnd
},
{
duration: 1000,
easing: linearEasing,
queue: false,
progress: function(elements, complete, remaining, start, tweenValue) {
dataS[complete] = scaleStart + ((scaleEnd - scaleStart) * complete);
}
});
Velocity(document.getElementById('movingBar'), {
translateX: translationEnd + '%',
tween: translationEnd
},
{
duration: 1000,
easing: translationEasing,
queue: false,
progress: function(elements, complete, remaining, start, tweenValue) {
dataT[complete] = translationStart + ((translationEnd - translationStart) * complete);
console.log("TWEEN", complete, tweenValue);
},
complete: function() {
//console.log("DONE!");
//console.log("SCALE:");
//if (desiredX in dataS) {
//console.log('Scale[0.5]: ', dataS[desiredX], ', Translation[0.5]: ', dataT[desiredX]);
//desiredYScale = dataS[desiredX];
//desiredYTranslation = dataT[desiredX];
//} else {
//animate();
//}
for (const key in dataS) {
if (dataS.hasOwnProperty(key)) {
//console.log('', key, ': ', dataS[key]);
}
}
//console.log("TRANSLATION:");
for (const key in dataT) {
if (dataT.hasOwnProperty(key)) {
//console.log('', key, ': ', dataT[key]);
}
}
}
});
};
/* Start animation on run. */
animate();
#root {
width: 700px;
height: 100%;
}
#container {
width: 100%;
height: 90px;
background-color: #000000;
}
.bar {
width: 100%;
height: 30px;
transform: scaleX(0.5) transform (calc(20 / 0.5 * 1%));
transform-origin: left;
background-color: #FF0000;
}
#description {
display: flex;
}
.percentage {
display: flex;
justify-content: flex-end;
width: 10%;
height: 20px;
text-align: right;
}
.odd {
background-color: #DDDDDD;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.2/velocity.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script>
<div id='root' style='width: 100%; height: 100%'>
<div id='container'>
<div class="bar" style="background-color: #00FF00; transform: scaleX(0.5) translateX(calc(0 / 0.5 * 1%))"></div>
<div id='movingBar' class="bar"></div>
<div class="bar" style="background-color: #00FF00; transform: scaleX(0.4) translateX(calc(10 / 0.4 * 1%))"></div>
</div>
<div id='description'>
<div class="percentage even">10%</div>
<div class="percentage odd">20%</div>
<div class="percentage even">30%</div>
<div class="percentage odd">40%</div>
<div class="percentage even">50%</div>
<div class="percentage odd">60%</div>
<div class="percentage even">70%</div>
<div class="percentage odd">80%</div>
<div class="percentage even">90%</div>
<div class="percentage odd">100%</div>
</div>
<button onClick="restart()">restart</button>
</div>