Files
taimed-international-app/uni_modules/nx-turn/components/nx-turn/js/nx-turn.js

777 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const PI = Math.PI;
/**
* 90度角弧度值
*/
const A90 = PI / 2;
let pageNumber = 0;
let pageCount = -1;
let moving = false;
export const point2D = (x, y) => {
return {
x: x,
y: y
};
}
export const setPageNumber = (number) => {
pageNumber = number;
}
export const setPageCount = (count) => {
pageCount = count;
}
let staticParam = {
mode: 'half',
page: {
width: 0,
height: 0
},
startPoint: point2D(0, 0),
pointVerticalPosition: "c",
moveHorizontalPosition: 'r',
pageCorner: null
}
const reset = () => {
staticParam = {
mode: staticParam.mode,
page: staticParam.page,
startPoint: point2D(0, 0),
pointVerticalPosition: "c",
moveHorizontalPosition: 'r',
pageCorner: null
}
moving = false;
ending = false;
return {
wrapper: {},
content: {},
bwrapper: {},
bcontent: {},
gradient: {},
bgradient: {}
};
}
/**
* 触摸开始事件
* @param page 包含页面的长和宽:{ width: w, height: h }
* @param startPoint 起始触摸点:{ x: x, y: y }
*/
export const touchStartEvent = (page, startPoint) => {
staticParam.page = page;
staticParam.startPoint = startPoint;
setVerticalPosition(page);
}
/**
* 触摸移动事件
* @param point 移动时触摸点:{ x: x, y: y }
* @returns Translate样式
*/
export const touchMoveEvent = (point) => {
getCornerPosition(point);
let style = {};
/*
* 满足以下条件,样式进行变化
*
* 从右翻页(向下一页)并且存在下一页
* pageCount == -1 表示 不限制向下翻页,即:永远存在下一页
* pageNumber < pageCount - 1 表示 当前页不是最后一页
* 或者
* 从左翻页(向上一页)并且存在上一页
* pageNumber > 0 表示 当前页不是第一页
* moving 表示 正在进行翻页动作(非第一次触摸移动)
*/
if (
(staticParam.moveHorizontalPosition == 'r' && (pageCount == -1 || pageNumber < pageCount - 1))
|| ((staticParam.moveHorizontalPosition == 'l') && (pageNumber > 0 || moving))
) {
style = getTranslateStyle(point);
if (staticParam.moveHorizontalPosition == 'l' && !moving) {
// 向上一页翻并且第一次触摸移动,翻动页面应展示上一页的数据
pageNumber--;
}
moving = true;
}
return {
style: style,
pageNumber: pageNumber
};
}
let ending = false;
/**
* 触摸结束事件
* @param point 结束时触摸点:{ x: x, y: y }
* @returns Translate样式
*/
export const touchEndEvent = (point, cb, stop, clickCenter) => {
if(ending) {
return;
}
ending = true;
let o;
let endPosition;
let turn = false;
let isClick = false;
// 取消从左翻页
let isCancel4l = false;
if (staticParam.pageCorner) {
endPosition = getEndPointPosition(point);
} else if (staticParam.mode == "half") {
// staticParam.pageCorner不存在说明触摸点未移动点击
isClick = true;
// 点击水平位置
staticParam.moveHorizontalPosition = getPointHorizontalPosition(point);
if (staticParam.moveHorizontalPosition != "c") {
// 点击右边向左翻页,反之
endPosition = staticParam.moveHorizontalPosition == "r" ? "l" : "r";
staticParam.pageCorner = "r";
staticParam.pointVerticalPosition = "c";
} else {
// 点击中间
reset();
clickCenter && clickCenter();
return;
}
}
// 翻页方向(从哪个方向翻页)
let pointHorizontalPosition = staticParam.moveHorizontalPosition;
// 如果是展示半本书模式,且从左翻页,设定为从右翻页
if (staticParam.mode == "half" && staticParam.moveHorizontalPosition == "l") {
pointHorizontalPosition = "r";
}
if (endPosition == pointHorizontalPosition) { // 触摸结束点位置与翻页方向相同
// 获取页面角落的坐标
o = getPageCornersCoordinates(staticParam.pageCorner, staticParam.page);
if (staticParam.moveHorizontalPosition == "l") {
if (!moving) {
// 向上翻页并且是首页
if (pageNumber == 0) {
staticParam.pageCorner = null;
if (stop) stop({
...animationOverStyle(),
isFirst: true
});
return;
}
pageNumber--;
}
}
} else { // 触摸结束点位置与翻页方向不同
// 获取结束点(翻开书页的书角顶点最终的位置)
o = getEndingPoint(staticParam.pageCorner, staticParam.page);
if (staticParam.moveHorizontalPosition == "r") {
if(pageCount != -1 && pageNumber >= pageCount - 1) {
staticParam.pageCorner = null;
if (stop) stop({
...animationOverStyle(),
isLast: true
});
return;
}
turn = true;
} else { // 取消翻页(从左翻页)
isCancel4l = true;
}
}
if (o) {
const to = [o.x, o.y];
createAnimation({
from: [point.x, point.y],
to: to,
duration: 500,
frame: (value) => {
if (cb) cb({
style: getTranslateStyle(point2D(value[0], value[1])),
pageNumber: pageNumber
});
},
complete: () => {
if ((staticParam.moveHorizontalPosition == "r" && turn) || isCancel4l) {
pageNumber++;
}
if (stop) stop(animationOverStyle());
}
});
}
}
const animationOverStyle = () => {
return {
style: reset(),
pageNumber: pageNumber
}
}
/**
* 获取页面对角线长度
* @param page 包含页面的长和宽:{ width: w, height: h }
*/
export const getPageDiagonalLength = (page) => {
return Math.sqrt(Math.pow(page.width, 2) + Math.pow(page.height, 2));
}
/**
* 获取Translate样式
* @param point 移动时触摸点:{ x: x, y: y }
*/
const getTranslateStyle = (point) => {
let computeResult, a, tr, position, origin;
/**
* 结束点(翻开书页的书角顶点最终的位置)
*/
const endingPoint = getEndingPoint(staticParam.pageCorner, staticParam.page);
switch (staticParam.pageCorner) {
case 'l':
return;
case 'r':
point.x = Math.max(Math.min(point.x, staticParam.page.width - 1), endingPoint.x);
computeResult = compute(point);
a = -radians2degrees(computeResult.alpha);
tr = point2D(-computeResult.tr.x, computeResult.tr.y);
position = [0, 0, 0, 1];
origin = [0, 0];
break;
case 'tl':
// point.x = Math.max(point.x, 1);
return;
case 'tr':
point.x = Math.max(Math.min(point.x, staticParam.page.width - 1), endingPoint.x);
computeResult = compute(point);
a = -radians2degrees(computeResult.alpha);
tr = point2D(-computeResult.tr.x, computeResult.tr.y);
position = [0, 0, 0, 1];
origin = [0, 0];
break;
case 'bl':
// point.x = Math.max(point.x, 1);
return;
case 'br':
point.x = Math.max(Math.min(point.x, staticParam.page.width - 1), endingPoint.x);
computeResult = compute(point);
a = radians2degrees(computeResult.alpha);
tr = point2D(-computeResult.tr.x, -computeResult.tr.y);
position = [0, 1, 1, 0]
origin = [0, 100];
break;
}
return transformStyle(a, tr, computeResult, position, origin);
}
/**
* 设置触摸在页面的上中下的位置,'t'、'c'、'b',分别代表上部、中部、下部。
* @param page 包含页面的长和宽:{ width: w, height: h }
* @param startPoint 起始触摸点:{ x: x, y: y }
*/
const setVerticalPosition = (page) => {
if (page.height * 0.3 > staticParam.startPoint.y) {
staticParam.pointVerticalPosition = 't';
} else if (page.height * 0.7 > staticParam.startPoint.y) {
staticParam.pointVerticalPosition = 'c';
} else if (page.height > staticParam.startPoint.y) {
staticParam.pointVerticalPosition = 'b';
}
}
/**
* 设置翻开页面页角的位置,'tl'、'tr'、'bl'、'br'、'l' 和 'r',分别代表左上角、右上角、左下角、右下角、左边和右边。
* @param point 移动触摸点:{ x: x, y: y }
*/
const setCornerPosition = (point) => {
if (staticParam.startPoint.x >= point.x) {
staticParam.moveHorizontalPosition = 'r';
} else {
staticParam.moveHorizontalPosition = 'l';
}
if (staticParam.moveHorizontalPosition == 'l' && staticParam.mode == 'half') {
staticParam.pageCorner = "r";
staticParam.pointVerticalPosition = "c";
} else if (staticParam.pointVerticalPosition == 'c') {
staticParam.pageCorner = staticParam.moveHorizontalPosition;
} else {
staticParam.pageCorner = staticParam.pointVerticalPosition + staticParam.moveHorizontalPosition;
}
}
/**
* 获取翻开页面页角的位置,'tl'、'tr'、'bl'、'br'、'l' 和 'r',分别代表左上角、右上角、左下角、右下角、左边和右边。
* @param point 移动触摸点:{ x: x, y: y }
*/
const getCornerPosition = (point) => {
if (!staticParam.pageCorner) {
setCornerPosition(point);
}
return staticParam.pageCorner;
}
/**
* 获取结束触摸点在页面的位置,'l'、'r',分别代表左边和右边。
* @param point 结束触摸点:{ x: x, y: y }
*/
const getEndPointPosition = (point) => {
let middle;
if (staticParam.mode == 'half') {
if (staticParam.moveHorizontalPosition == 'l') {
middle = staticParam.page.width * 0.2
} else {
middle = staticParam.page.width * 0.8
}
}
if (middle > point.x) {
return 'l';
} else {
return 'r';
}
}
/**
* 获取触摸点在页面的位置,'l'、'c'、'r',分别代表左边,中间和右边。
* @param point 触摸点:{ x: x, y: y }
*/
const getPointHorizontalPosition = (point) => {
if (staticParam.page.width * 0.3 > point.x) {
return 'l';
}
if (staticParam.page.width * 0.7 > point.x) {
return 'c';
} else {
return 'r';
}
}
const compute = (point) => {
const page = staticParam.page;
const top = staticParam.pointVerticalPosition == 't';
const center = staticParam.pointVerticalPosition == "c"
const left = staticParam.mode != 'half' && staticParam.moveHorizontalPosition == 'l';
const pageCorner = getCornerPosition(point);
// 获取页面角落的坐标
const o = getPageCornersCoordinates(pageCorner, page);
const pageDiagonalLength = getPageDiagonalLength(page);
// 触摸/鼠标点相对于页面角落的坐标
const rel = point2D(0, 0);
rel.x = (o.x) ? o.x - point.x : point.x;
rel.y = (o.y) ? o.y - point.y : point.y;
// 触摸/鼠标点与页面角落的中间点坐标
const middle = point2D(0, 0);
middle.x = (left) ? page.width - rel.x / 2 : point.x + rel.x / 2;
middle.y = rel.y / 2;
/**
* 计算触摸/鼠标点与y轴的夹角弧度值alpha(α)。
* 其中,
* A90是一个常量表示90度的角度弧度值
* Math.atan2函数返回由两个参数(y和x)确定的点与x轴的夹角弧度值范围为[-π, π]。
*/
const alpha = center ? A90 : A90 - Math.atan2(rel.y, rel.x);
/** ??
* 计算弧度值gamma(γ)gamma的计算方式为alpha减去middle点与x轴的夹角弧度值(反正切值, (0,0)为原点)
*/
const gamma = alpha - Math.atan2(middle.y, middle.x);
// console.log("gamma:", gamma)
/** ??
* 计算动态的距离其中点middle的坐标为(x, y)gamma为一个角度值。具体实现过程如下
* 首先根据middle点的坐标计算出该点到原点(0,0)的距离即sqrt(x^2 + y^2)。
* 然后根据gamma的正弦值计算出一个缩放因子即sin(gamma)。
* 最后将距离与缩放因子相乘并取最大值为0得到最终的距离值。
* 作用是根据给定的角度和一个点的坐标,计算出一个距离值,该距离值表示了该点到原点的最远距离。
*/
const distance = Math.max(0, Math.sin(gamma) * Math.sqrt(Math.pow(middle.x, 2) + Math.pow(middle.y, 2)));
// console.log("distance:", distance)
/** ??
* 根据middle点到原点的距离和alpha角度计算并返回一个二维点。
*/
const tr = point2D(distance * Math.sin(alpha), distance * Math.cos(alpha));
// console.log("tr:", tr)
if (alpha > A90) {
tr.x = tr.x + Math.abs(tr.y * rel.y / rel.x);
tr.y = 0;
if (Math.round(tr.x * Math.tan(PI - alpha)) < page.height) {
point.y = Math.sqrt(Math.pow(page.height, 2) + 2 * middle.x * rel.x);
if (top) point.y = page.height - point.y;
return compute(point);
}
}
const mv = point2D(0, 0);
if (alpha > A90) {
const beta = PI - alpha;
const dd = pageDiagonalLength - page.height / Math.sin(beta);
mv.x = Math.round(dd * Math.cos(beta));
mv.y = Math.round(dd * Math.sin(beta));
if (left) mv.x = -mv.x;
if (top) mv.y = -mv.y;
}
/**
* 中缝(订口边)与上边(从上翻)或下边(从下翻)(书顶或书底)未翻开的长度
*/
const px = Math.round(tr.y / Math.tan(alpha) + tr.x);
/**
* 上边(从上翻)或下边(从下翻)(书顶或书底)翻开的长度
*/
const side = page.width - px;
/**
* 翻开书页的书角顶点与上边(从上翻)或下边(从下翻)(书顶或书底)的距离
*/
const sideX = side * Math.cos(alpha * 2);
/**
* 翻开书页的顶点与翻口的距离
*/
const sideY = side * Math.sin(alpha * 2);
/**
* 翻开书页的书角顶点与中缝(订口边)和上边(从下翻)或下边(从上翻)(书顶或书底)的相对距离
*/
const df = point2D(
Math.round((left ? side - sideX : px + sideX)),
Math.round(top ? sideY : center ? 0 : page.height - sideY)
);
/**
* 翻开的斜边长度
*/
const gradientSize = side * Math.sin(alpha);
/**
* 结束点(翻开书页的书角顶点最终的位置)
*/
const endingPoint = getEndingPoint(pageCorner, page);
const far = Math.sqrt(Math.pow(endingPoint.x - point.x, 2) + (center ? 0 : Math.pow(endingPoint.y - point.y,
2))) / page.width;
/**
* 翻开书角的阴影透明度
*/
const shadowVal = Math.sin(A90 * ((far > 1) ? 2 - far : far));
/**
* 翻开书角的斜边阴影透明度
*/
const gradientOpacity = Math.min(far, 1);
/**
* 翻开书角的斜边阴影颜色初始透明度
*/
const gradientStartVal = gradientSize > 100 ? (gradientSize - 100) / gradientSize : 0;
/**
*
*/
const gradientEndPointA = point2D(
gradientSize * Math.sin(alpha) / page.width * 100,
gradientSize * Math.cos(alpha) / page.height * 100);
if (center) gradientEndPointA.y = 100 - gradientEndPointA.y;
/**
*
*/
const gradientEndPointB = point2D(
gradientSize * 1.2 * Math.sin(alpha) / page.width * 100,
gradientSize * 1.2 * Math.cos(alpha) / page.height * 100);
if (!left) gradientEndPointB.x = 100 - gradientEndPointB.x;
if (!top) gradientEndPointB.y = 100 - gradientEndPointB.y;
tr.x = Math.round(tr.x);
tr.y = Math.round(tr.y);
return {
alpha: alpha,
df: df,
tr: tr,
mv: mv,
shadowVal: shadowVal,
gradientOpacity: gradientOpacity,
gradientStartVal: gradientStartVal,
gradientEndPointA: gradientEndPointA,
gradientEndPointB: gradientEndPointB
}
}
/**
* 转换样式
* @param page 包含页面的长和宽:{ width: w, height: h }
* @param a 角度
* @param tr
* @param computeResult 计算结果
* @param position 定位,数字数组:[left, top, right, bootom]取值01001auto
* @param origin 变换原点,数字数组:[x, y]
*/
const transformStyle = (a, tr, computeResult, position, origin) => {
const top = staticParam.pointVerticalPosition == 't';
const center = staticParam.pointVerticalPosition == "c"
const left = staticParam.mode != 'half' && staticParam.moveHorizontalPosition == 'l';
const page = staticParam.page;
const {
df,
mv,
shadowVal,
gradientOpacity,
gradientStartVal,
gradientEndPointA,
gradientEndPointB
} = computeResult;
// 获取页面对角线长度
const pageDiagonalLength = getPageDiagonalLength(page);
// 定位选项
const positionOpt = ['0', 'auto'];
const mvW = (page.width - pageDiagonalLength) * origin[0] / 100;
const mvH = (page.height - pageDiagonalLength) * origin[1] / 100;
const positionStyle = {
left: positionOpt[position[0]],
top: positionOpt[position[1]],
right: positionOpt[position[2]],
bottom: positionOpt[position[3]]
};
const aliasingFk = (a != 90 && a != -90) ? 1 : 0;
const transformOrigin = origin[0] + '% ' + origin[1] + '%';
const style = {};
// 页面正面外框样式
style.wrapper = {
transform: translate(-tr.x + mvW - aliasingFk, -tr.y + mvH) + rotate(-a),
transformOrigin: transformOrigin,
};
// 页面正面内容框样式
style.content = {
...positionStyle,
transform: rotate(a) + translate(tr.x + aliasingFk, tr.y),
transformOrigin: transformOrigin,
};
// 页面背面外框样式
style.bwrapper = {
transform: translate(-tr.x + mv.x + mvW, -tr.y + mv.y + mvH) + rotate(-a),
transformOrigin: transformOrigin,
};
// 页面背面内容框样式
style.bcontent = {
...positionStyle,
transform: rotate(a) +
translate(tr.x + df.x - mv.x - page.width * origin[0] / 100, tr.y + df.y - mv.y - page.height *
origin[1] /
100) +
rotate((180 / a - 2) * a),
transformOrigin: transformOrigin,
boxShadow: '0 0 20px rgba(0,0,0,' + Math.max(0.3, 0.5 * shadowVal) + ')'
};
if (origin[0])
gradientEndPointA.x = 100 - gradientEndPointA.x;
if (origin[1])
gradientEndPointA.y = 100 - gradientEndPointA.y;
// 翻开斜边样式
style.gradient = gradientStyle(page,
point2D(left ? 0 : 100, top ? 0 : 100),
point2D(gradientEndPointB.x, gradientEndPointB.y),
[
[0.6, 'rgba(0,0,0,0)'],
[0.8, 'rgba(0,0,0,' + (0.3 * gradientOpacity) + ')'],
[1, 'rgba(0,0,0,0)']
],
3);
// 翻开背面斜边样式
style.bgradient = gradientStyle(page,
point2D(left ? 100 : 0, top ? 0 : 100),
point2D(gradientEndPointA.x, gradientEndPointA.y),
[
[gradientStartVal, 'rgba(0,0,0,0)'],
[((1 - gradientStartVal) * 0.8) + gradientStartVal, 'rgba(0,0,0,' + (0.2 * gradientOpacity) +
')'
],
[1, 'rgba(255,255,255,' + (0.2 * gradientOpacity) + ')']
],
3);
return style;
}
const gradientStyle = (page, p0, p1, colors, numColors) => {
let j;
const cols = [];
// for (j = 0; j < numColors; j++) {
// cols.push('color-stop(' + colors[j][0] + ', ' + colors[j][1] + ')');
// }
// return {
// backgroundImage: '-webkit-gradient(linear, ' +
// p0.x + '% ' +
// p0.y + '%,' +
// p1.x + '% ' +
// p1.y + '%, ' +
// cols.join(',') + ' )'
// }
p0 = {
x: p0.x / 100 * page.width,
y: p0.y / 100 * page.height
};
p1 = {
x: p1.x / 100 * page.width,
y: p1.y / 100 * page.height
};
const dx = p1.x - p0.x;
const dy = p1.y - p0.y;
const angle = Math.atan2(dy, dx);
const angle2 = angle - Math.PI / 2;
const diagonal = Math.abs(page.width * Math.sin(angle2)) + Math.abs(page.height * Math.cos(angle2));
const gradientDiagonal = Math.sqrt(Math.pow(dy, 2) + Math.pow(dx, 2));
const corner = point2D((p1.x < p0.x) ? page.width : 0, (p1.y <= p0.y) ? page.height : 0);
const slope = Math.tan(angle);
const inverse = slope == 0 ? 0 : -1 / slope;
const x = inverse - slope == 0 ? 0 : (inverse * corner.x - corner.y - slope * p0.x + p0.y) / (inverse - slope);
const c = {
x: x,
y: inverse * x - inverse * corner.x + corner.y
};
const segA = (Math.sqrt(Math.pow(c.x - p0.x, 2) + Math.pow(c.y - p0.y, 2)));
for (j = 0; j < numColors; j++) {
cols.push(' ' + colors[j][1] + ' ' + ((segA + gradientDiagonal * colors[j][0]) * 100 / diagonal) + '%');
}
return {
backgroundImage: 'linear-gradient(' + (Math.PI / 2 + angle) + 'rad,' + cols.join(',') + ')'
}
}
/**
* 将角度从弧度转换为度
* @param radians
*/
const radians2degrees = (radians) => {
return radians / PI * 180;
}
/**
* 返回CSS旋转值
*/
const rotate = (degrees) => {
return ' rotate(' + degrees + 'deg) ';
}
/**
* 返回CSS变换值
*/
const translate = (x, y) => {
return ' translate(' + x + 'px, ' + y + 'px) ';
}
/**
* 获取页面角落坐标
* @param corner 角落位置,'tl'、'tr'、'bl'、'br'、'l' 和 'r',分别代表左上角、右上角、左下角、右下角、左边和右边。
* @param page 包含页面的长和宽:{ width: w, height: h }
* @param opts 偏移量
*/
const getPageCornersCoordinates = (corner, page, opts) => {
opts = opts || 0;
switch (corner) {
case 'tl': // 左上角
return point2D(opts, opts);
case 'tr': // 右上角
return point2D(page.width - opts, opts);
case 'bl': // 左下角
return point2D(opts, page.height - opts);
case 'br': // 右下角
return point2D(page.width - opts, page.height - opts);
case 'l': // 左边
return point2D(opts, 0);
case 'r': // 右边
return point2D(page.width - opts, 0);
}
}
/**
* 获取结束点(翻开书页的书角顶点最终的位置)
* @param corner 角落位置,'tl'、'tr'、'bl'、'br'、'l' 和 'r',分别代表左上角、右上角、左下角、右下角、左边和右边。
* @param page 包含页面的长和宽:{ width: w, height: h }
*/
const getEndingPoint = (corner, page) => {
switch (corner) {
case 'tl':
return point2D(page.width * 2, 0);
case 'tr':
return point2D(-page.width, 0);
case 'bl':
return point2D(page.width * 2, page.height);
case 'br':
return point2D(-page.width, page.height);
case 'l':
if (staticParam.mode == 'half') {
return point2D(page.width, 0);
} else {
return point2D(page.width * 2, 0);
}
case 'r':
return point2D(-page.width, 0);
}
}
const requestAnimation = (callback) => {
let windowRequestAnimationFrame;
if (window) {
windowRequestAnimationFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame;
}
if (windowRequestAnimationFrame) {
windowRequestAnimationFrame(callback);
} else {
setTimeout(callback, 1000 / 60);
}
}
/**
* 创建动画
*/
const createAnimation = (param) => {
if (param) {
// 动画开始时间
const startTime = new Date().getTime();
param.duration = param.duration || 0;
param.count = 0;
animationFun(startTime, param);
}
}
const animationFun = (startTime, param) => {
// 动画已执行时间
let timeDiff = Math.min(param.duration, (new Date()).getTime() - startTime);
// 变化值
const transformValues = [];
if (param.from && param.to) {
if (!param.from.length) param.from = [param.from];
if (!param.to.length) param.to = [param.to];
// 最小变换参数长度
const valueLength = Math.min(param.from.length, param.to.length);
// 计算当前执行的参数值
for (let i = 0; i < valueLength; i++) {
transformValues.push(easing(timeDiff, param.from[i], param.to[i], param.duration));
}
}
if (param.frame) param.frame((transformValues.length == 1) ? transformValues[0] : transformValues);
// 判断动画是否结束
if (timeDiff == param.duration) {
if (param.complete) param.complete();
} else {
requestAnimation(() => {
animationFun(startTime, param);
});
}
}
const easing = (timeDiff, from, to, duration) => {
const t = timeDiff / duration - 1;
return (to - from) * Math.sqrt(1 - Math.pow(t, 2)) + from;
}