motion tween
This commit is contained in:
parent
d162a9599b
commit
95834bb0e9
257
src/main.js
257
src/main.js
|
|
@ -3,7 +3,7 @@ import * as fitCurve from '/fit-curve.js';
|
||||||
import { Bezier } from "/bezier.js";
|
import { Bezier } from "/bezier.js";
|
||||||
import { Quadtree } from './quadtree.js';
|
import { Quadtree } from './quadtree.js';
|
||||||
import { createNewFileDialog, showNewFileDialog, closeDialog } from './newfile.js';
|
import { createNewFileDialog, showNewFileDialog, closeDialog } from './newfile.js';
|
||||||
import { titleCase, getMousePositionFraction } from './utils.js';
|
import { titleCase, getMousePositionFraction, getKeyframesSurrounding } from './utils.js';
|
||||||
const { writeTextFile: writeTextFile, readTextFile: readTextFile }= window.__TAURI__.fs;
|
const { writeTextFile: writeTextFile, readTextFile: readTextFile }= window.__TAURI__.fs;
|
||||||
const {
|
const {
|
||||||
open: openFileDialog,
|
open: openFileDialog,
|
||||||
|
|
@ -240,43 +240,49 @@ let actions = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addImageObject: {
|
addImageObject: {
|
||||||
create: (x, y, img, parent) => {
|
create: (x, y, imgsrc, ix, parent) => {
|
||||||
redoStack.length = 0; // Clear redo stack
|
redoStack.length = 0; // Clear redo stack
|
||||||
let action = {
|
let action = {
|
||||||
shapeUuid: uuidv4(),
|
shapeUuid: uuidv4(),
|
||||||
objectUuid: uuidv4(),
|
objectUuid: uuidv4(),
|
||||||
x: x,
|
x: x,
|
||||||
y: y,
|
y: y,
|
||||||
width: img.width,
|
src: imgsrc,
|
||||||
height: img.height,
|
ix: ix,
|
||||||
ix: img.ix,
|
|
||||||
img: img.idx,
|
|
||||||
parent: parent.idx
|
parent: parent.idx
|
||||||
|
|
||||||
}
|
}
|
||||||
undoStack.push({name: "addImageObject", action: action})
|
undoStack.push({name: "addImageObject", action: action})
|
||||||
actions.addImageObject.execute(action)
|
actions.addImageObject.execute(action)
|
||||||
},
|
},
|
||||||
execute: (action) => {
|
execute: (action) => {
|
||||||
let imageObject = new GraphicsObject(action.objectUuid)
|
let imageObject = new GraphicsObject(action.objectUuid)
|
||||||
let img = pointerList[action.img]
|
// let img = pointerList[action.img]
|
||||||
|
let img = new Image();
|
||||||
|
img.onload = function() {
|
||||||
let ct = {
|
let ct = {
|
||||||
...context,
|
...context,
|
||||||
fillImage: img,
|
fillImage: img,
|
||||||
strokeShape: false,
|
strokeShape: false,
|
||||||
}
|
}
|
||||||
let imageShape = new Shape(0, 0, ct, action.shapeUuid)
|
let imageShape = new Shape(0, 0, ct, action.shapeUuid)
|
||||||
imageShape.addLine(action.width, 0)
|
imageShape.addLine(img.width, 0)
|
||||||
imageShape.addLine(action.width, action.height)
|
imageShape.addLine(img.width, img.height)
|
||||||
imageShape.addLine(0, action.height)
|
imageShape.addLine(0, img.height)
|
||||||
imageShape.addLine(0, 0)
|
imageShape.addLine(0, 0)
|
||||||
imageShape.update()
|
imageShape.update()
|
||||||
|
imageShape.regions[0].fillImage = img
|
||||||
|
imageShape.regions[0].filled = true
|
||||||
imageObject.addShape(imageShape)
|
imageObject.addShape(imageShape)
|
||||||
let parent = pointerList[action.parent]
|
let parent = pointerList[action.parent]
|
||||||
parent.addObject(
|
parent.addObject(
|
||||||
imageObject,
|
imageObject,
|
||||||
action.x-action.width/2 + (20*action.ix),
|
action.x-img.width/2 + (20*action.ix),
|
||||||
action.y-action.height/2 + (20*action.ix)
|
action.y-img.height/2 + (20*action.ix)
|
||||||
)
|
)
|
||||||
|
updateUI();
|
||||||
|
}
|
||||||
|
img.src = action.src
|
||||||
},
|
},
|
||||||
rollback: (action) => {
|
rollback: (action) => {
|
||||||
let shape = pointerList[action.shapeUuid]
|
let shape = pointerList[action.shapeUuid]
|
||||||
|
|
@ -312,6 +318,111 @@ let actions = {
|
||||||
frame.keys = structuredClone(action.oldState)
|
frame.keys = structuredClone(action.oldState)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
addFrame: {
|
||||||
|
create: () => {
|
||||||
|
redoStack.length = 0
|
||||||
|
let frames = []
|
||||||
|
for (let i=context.activeObject.activeLayer.frames.length; i<=context.activeObject.currentFrameNum; i++) {
|
||||||
|
frames.push(uuidv4())
|
||||||
|
}
|
||||||
|
let action = {
|
||||||
|
frames: frames,
|
||||||
|
layer: context.activeObject.activeLayer.idx
|
||||||
|
}
|
||||||
|
undoStack.push({name: 'addFrame', action: action})
|
||||||
|
actions.addFrame.execute(action)
|
||||||
|
},
|
||||||
|
execute: (action) => {
|
||||||
|
let layer = pointerList[action.layer]
|
||||||
|
for (let frame of action.frames) {
|
||||||
|
layer.frames.push(new Frame("normal", frame))
|
||||||
|
}
|
||||||
|
updateLayers()
|
||||||
|
},
|
||||||
|
rollback: (action) => {
|
||||||
|
let layer = pointerList[action.layer]
|
||||||
|
for (let _frame of action.frames) {
|
||||||
|
layer.frames.pop()
|
||||||
|
}
|
||||||
|
updateLayers()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addKeyframe: {
|
||||||
|
create: () => {
|
||||||
|
let frameNum = context.activeObject.currentFrameNum
|
||||||
|
let layer = context.activeObject.activeLayer
|
||||||
|
let formerType;
|
||||||
|
let addedFrames = 0;
|
||||||
|
if (frameNum >= layer.frames.length) {
|
||||||
|
formerType = "none"
|
||||||
|
addedFrames = frameNum - layer.frames.length
|
||||||
|
} else if (layer.frames[frameNum].frameType != "keyframe") {
|
||||||
|
formerType = layer.frames[frameNum].frameType
|
||||||
|
} else {
|
||||||
|
console.log("foolish")
|
||||||
|
return // Already a keyframe, nothing to do
|
||||||
|
}
|
||||||
|
redoStack.length = 0
|
||||||
|
let action = {
|
||||||
|
frameNum: frameNum,
|
||||||
|
object: context.activeObject.idx,
|
||||||
|
layer: layer.idx,
|
||||||
|
formerType: formerType,
|
||||||
|
addedFrames: addedFrames
|
||||||
|
}
|
||||||
|
undoStack.push({name: 'addKeyframe', action: action})
|
||||||
|
actions.addKeyframe.execute(action)
|
||||||
|
},
|
||||||
|
execute: (action) => {
|
||||||
|
// your code here
|
||||||
|
let object = pointerList[action.object]
|
||||||
|
let layer = pointerList[action.layer]
|
||||||
|
let latestFrame = object.getFrame(Math.max(action.frameNum-1, 0))
|
||||||
|
let newKeyframe = new Frame("keyframe")
|
||||||
|
for (let key in latestFrame.keys) {
|
||||||
|
newKeyframe.keys[key] = structuredClone(latestFrame.keys[key])
|
||||||
|
}
|
||||||
|
for (let shape of latestFrame.shapes) {
|
||||||
|
newKeyframe.shapes.push(shape.copy())
|
||||||
|
}
|
||||||
|
if (action.frameNum >= layer.frames.length) {
|
||||||
|
for (let i=layer.frames.length; i<action.frameNum; i++) {
|
||||||
|
layer.frames.push(new Frame())
|
||||||
|
}
|
||||||
|
layer.frames.push(newKeyframe)
|
||||||
|
} else if (layer.frames[action.frameNum].frameType != "keyframe") {
|
||||||
|
layer.frames[action.frameNum] = newKeyframe
|
||||||
|
}
|
||||||
|
updateLayers()
|
||||||
|
},
|
||||||
|
rollback: (action) => {
|
||||||
|
let layer = pointerList[action.layer]
|
||||||
|
if (action.formerType == "none") {
|
||||||
|
for (let i=0; i<action.addedFrames+1; i++) {
|
||||||
|
layer.frames.pop()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let layer = pointerList[action.layer]
|
||||||
|
layer.frames[action.frameNum].frameType = action.formerType
|
||||||
|
}
|
||||||
|
updateLayers()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addMotionTween: {
|
||||||
|
create: () => {
|
||||||
|
redoStack.length = 0
|
||||||
|
let action = {
|
||||||
|
}
|
||||||
|
undoStack.push({name: 'addMotionTween', action: action})
|
||||||
|
actions.addMotionTween.execute(action)
|
||||||
|
},
|
||||||
|
execute: (action) => {
|
||||||
|
// your code here
|
||||||
|
},
|
||||||
|
rollback: (action) => {
|
||||||
|
// your code here
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
function uuidv4() {
|
function uuidv4() {
|
||||||
|
|
@ -919,7 +1030,7 @@ class Shape {
|
||||||
ctx.lineCap = "round"
|
ctx.lineCap = "round"
|
||||||
for (let region of this.regions) {
|
for (let region of this.regions) {
|
||||||
// if (region.filled) continue;
|
// if (region.filled) continue;
|
||||||
if (region.fillStyle && region.filled) {
|
if ((region.fillStyle || region.fillImage) && region.filled) {
|
||||||
// ctx.fillStyle = region.fill
|
// ctx.fillStyle = region.fill
|
||||||
if (region.fillImage) {
|
if (region.fillImage) {
|
||||||
let pat = ctx.createPattern(region.fillImage, "no-repeat")
|
let pat = ctx.createPattern(region.fillImage, "no-repeat")
|
||||||
|
|
@ -937,6 +1048,7 @@ class Shape {
|
||||||
ctx.fill()
|
ctx.fill()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.stroked) {
|
||||||
for (let curve of this.curves) {
|
for (let curve of this.curves) {
|
||||||
ctx.strokeStyle = curve.color
|
ctx.strokeStyle = curve.color
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
|
|
@ -951,6 +1063,7 @@ class Shape {
|
||||||
// ctx.arc(curve.points[3].x,curve.points[3].y, 3, 0, 2*Math.PI)
|
// ctx.arc(curve.points[3].x,curve.points[3].y, 3, 0, 2*Math.PI)
|
||||||
// ctx.fill()
|
// ctx.fill()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Debug, show quadtree
|
// Debug, show quadtree
|
||||||
// this.quadtree.draw(ctx)
|
// this.quadtree.draw(ctx)
|
||||||
|
|
||||||
|
|
@ -981,26 +1094,46 @@ class GraphicsObject {
|
||||||
return this.layers[this.currentLayer]
|
return this.layers[this.currentLayer]
|
||||||
}
|
}
|
||||||
get children() {
|
get children() {
|
||||||
return this.layers[this.currentLayer].children
|
return this.activeLayer.children
|
||||||
}
|
}
|
||||||
get currentFrame() {
|
get currentFrame() {
|
||||||
return this.getFrame(this.currentFrameNum)
|
return this.getFrame(this.currentFrameNum)
|
||||||
}
|
}
|
||||||
getFrame(num) {
|
getFrame(num) {
|
||||||
if (this.layers[this.currentLayer].frames[num]) {
|
if (this.activeLayer.frames[num]) {
|
||||||
if (this.layers[this.currentLayer].frames[num].frameType == "keyframe") {
|
if (this.activeLayer.frames[num].frameType == "keyframe") {
|
||||||
return this.layers[this.currentLayer].frames[num]
|
return this.activeLayer.frames[num]
|
||||||
} else if (this.layers[this.currentLayer].frames[num].frameType == "motion") {
|
} else if (this.activeLayer.frames[num].frameType == "motion") {
|
||||||
|
let frameKeys = {}
|
||||||
|
const t = (num - this.activeLayer.frames[num].prevIndex) / (this.activeLayer.frames[num].nextIndex - this.activeLayer.frames[num].prevIndex);
|
||||||
|
console.log(this.activeLayer.frames[num].prev)
|
||||||
|
for (let key in this.activeLayer.frames[num].prev.keys) {
|
||||||
|
frameKeys[key] = {}
|
||||||
|
let prevKeyDict = this.activeLayer.frames[num].prev.keys[key]
|
||||||
|
let nextKeyDict = this.activeLayer.frames[num].next.keys[key]
|
||||||
|
for (let prop in prevKeyDict) {
|
||||||
|
frameKeys[key][prop] = (1 - t) * prevKeyDict[prop] + t * nextKeyDict[prop];
|
||||||
|
}
|
||||||
|
|
||||||
} else if (this.layers[this.currentLayer].frames[num].frameType == "shape") {
|
}
|
||||||
|
let frame = new Frame("motion", "temp")
|
||||||
|
frame.keys = frameKeys
|
||||||
|
return frame
|
||||||
|
} else if (this.activeLayer.frames[num].frameType == "shape") {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
for (let i=num; i>=0; i--) {
|
for (let i=Math.min(num, this.activeLayer.frames.length-1); i>=0; i--) {
|
||||||
if (this.layers[this.currentLayer].frames[i].frameType == "keyframe") {
|
if (this.activeLayer.frames[i].frameType == "keyframe") {
|
||||||
return this.layers[this.currentLayer].frames[i]
|
return this.activeLayer.frames[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
for (let i=Math.min(num, this.activeLayer.frames.length-1); i>=0; i--) {
|
||||||
|
if (this.activeLayer.frames[i].frameType == "keyframe") {
|
||||||
|
return this.activeLayer.frames[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
get maxFrame() {
|
get maxFrame() {
|
||||||
|
|
@ -1032,9 +1165,9 @@ class GraphicsObject {
|
||||||
let ctx = context.ctx;
|
let ctx = context.ctx;
|
||||||
ctx.translate(this.x, this.y)
|
ctx.translate(this.x, this.y)
|
||||||
ctx.rotate(this.rotation)
|
ctx.rotate(this.rotation)
|
||||||
if (this.currentFrameNum>=this.maxFrame) {
|
// if (this.currentFrameNum>=this.maxFrame) {
|
||||||
this.currentFrameNum = 0;
|
// this.currentFrameNum = 0;
|
||||||
}
|
// }
|
||||||
for (let shape of this.currentFrame.shapes) {
|
for (let shape of this.currentFrame.shapes) {
|
||||||
shape.draw(context)
|
shape.draw(context)
|
||||||
}
|
}
|
||||||
|
|
@ -1361,31 +1494,30 @@ async function quit() {
|
||||||
|
|
||||||
function addFrame() {
|
function addFrame() {
|
||||||
if (context.activeObject.currentFrameNum >= context.activeObject.activeLayer.frames.length) {
|
if (context.activeObject.currentFrameNum >= context.activeObject.activeLayer.frames.length) {
|
||||||
for (let i=context.activeObject.activeLayer.frames.length; i<=context.activeObject.currentFrameNum; i++) {
|
actions.addFrame.create()
|
||||||
context.activeObject.activeLayer.frames.push(new Frame())
|
|
||||||
}
|
|
||||||
updateLayers()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addKeyframe() {
|
function addKeyframe() {
|
||||||
let newKeyframe = new Frame("keyframe")
|
console.log(context.activeObject.currentFrameNum)
|
||||||
let latestFrame = context.activeObject.getFrame(Math.max(context.activeObject.currentFrameNum-1, 0))
|
actions.addKeyframe.create()
|
||||||
for (let key in latestFrame.keys) {
|
|
||||||
newKeyframe.keys[key] = latestFrame.keys[key]
|
|
||||||
}
|
}
|
||||||
for (let shape of latestFrame.shapes) {
|
|
||||||
newKeyframe.shapes.push(shape.copy())
|
function addMotionTween() {
|
||||||
|
let frames = context.activeObject.activeLayer.frames
|
||||||
|
let currentFrame = context.activeObject.currentFrameNum
|
||||||
|
let {lastKeyframeBefore, firstKeyframeAfter} = getKeyframesSurrounding(frames, currentFrame)
|
||||||
|
if ((lastKeyframeBefore != undefined) && (firstKeyframeAfter != undefined)) {
|
||||||
|
for (let i=lastKeyframeBefore + 1; i<firstKeyframeAfter; i++) {
|
||||||
|
frames[i].frameType = "motion"
|
||||||
|
frames[i].prev = frames[lastKeyframeBefore]
|
||||||
|
frames[i].next = frames[firstKeyframeAfter]
|
||||||
|
frames[i].prevIndex = lastKeyframeBefore
|
||||||
|
frames[i].nextIndex = firstKeyframeAfter
|
||||||
}
|
}
|
||||||
if (context.activeObject.currentFrameNum >= context.activeObject.activeLayer.frames.length) {
|
|
||||||
for (let i=context.activeObject.activeLayer.frames.length; i<context.activeObject.currentFrameNum; i++) {
|
|
||||||
context.activeObject.activeLayer.frames.push(new Frame())
|
|
||||||
}
|
|
||||||
context.activeObject.activeLayer.frames.push(newKeyframe)
|
|
||||||
} else if (context.activeObject.activeLayer.frames[context.activeObject.currentFrameNum].frameType != "keyframe") {
|
|
||||||
context.activeObject.activeLayer.frames[context.activeObject.currentFrameNum] = newKeyframe
|
|
||||||
}
|
}
|
||||||
updateLayers()
|
updateLayers()
|
||||||
|
console.log(frames)
|
||||||
}
|
}
|
||||||
|
|
||||||
function stage() {
|
function stage() {
|
||||||
|
|
@ -1407,16 +1539,26 @@ function stage() {
|
||||||
if (item.kind == "file") {
|
if (item.kind == "file") {
|
||||||
let file = item.getAsFile()
|
let file = item.getAsFile()
|
||||||
if (imageTypes.includes(file.type)) {
|
if (imageTypes.includes(file.type)) {
|
||||||
let img = new Image()
|
let img = new Image();
|
||||||
img.src = window.URL.createObjectURL(file)
|
let reader = new FileReader();
|
||||||
img.ix = i
|
|
||||||
img.idx = uuidv4()
|
// Read the file as a data URL
|
||||||
pointerList[img.idx] = img
|
reader.readAsDataURL(file);
|
||||||
img.onload = function() {
|
reader.ix = i
|
||||||
|
|
||||||
|
reader.onload = function(event) {
|
||||||
|
let imgsrc = event.target.result; // This is the data URL
|
||||||
|
// console.log(imgsrc)
|
||||||
|
|
||||||
|
// img.onload = function() {
|
||||||
actions.addImageObject.create(
|
actions.addImageObject.create(
|
||||||
mouse.x, mouse.y, img, context.activeObject)
|
mouse.x, mouse.y, imgsrc, reader.ix, context.activeObject);
|
||||||
updateUI()
|
// };
|
||||||
}
|
};
|
||||||
|
|
||||||
|
reader.onerror = function(error) {
|
||||||
|
console.error("Error reading file as data URL", error);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
@ -2043,8 +2185,10 @@ function updateLayers() {
|
||||||
let mouse = getMousePos(layerTrack, e)
|
let mouse = getMousePos(layerTrack, e)
|
||||||
let frameNum = parseInt(mouse.x/25)
|
let frameNum = parseInt(mouse.x/25)
|
||||||
context.activeObject.currentFrameNum = frameNum
|
context.activeObject.currentFrameNum = frameNum
|
||||||
|
console.log(context.activeObject )
|
||||||
updateLayers()
|
updateLayers()
|
||||||
updateMenu()
|
updateMenu()
|
||||||
|
updateUI()
|
||||||
})
|
})
|
||||||
let highlightedFrame = false
|
let highlightedFrame = false
|
||||||
layer.frames.forEach((frame, i) => {
|
layer.frames.forEach((frame, i) => {
|
||||||
|
|
@ -2055,10 +2199,8 @@ function updateLayers() {
|
||||||
frameEl.classList.add("active")
|
frameEl.classList.add("active")
|
||||||
highlightedFrame = true
|
highlightedFrame = true
|
||||||
}
|
}
|
||||||
console.log(frame.frameType)
|
|
||||||
if (frame.frameType == "keyframe") {
|
frameEl.classList.add(frame.frameType)
|
||||||
frameEl.classList.add("keyframe")
|
|
||||||
}
|
|
||||||
layerTrack.appendChild(frameEl)
|
layerTrack.appendChild(frameEl)
|
||||||
})
|
})
|
||||||
if (!highlightedFrame) {
|
if (!highlightedFrame) {
|
||||||
|
|
@ -2172,6 +2314,11 @@ async function updateMenu() {
|
||||||
newFrameMenuItem,
|
newFrameMenuItem,
|
||||||
newKeyframeMenuItem,
|
newKeyframeMenuItem,
|
||||||
deleteFrameMenuItem,
|
deleteFrameMenuItem,
|
||||||
|
{
|
||||||
|
text: "Add Motion Tween",
|
||||||
|
enabled: activeFrame && (!activeKeyframe),
|
||||||
|
action: addMotionTween
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: "Return to start",
|
text: "Return to start",
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
|
|
||||||
|
|
@ -305,6 +305,21 @@ button {
|
||||||
background-color: #222; /* Set the color of the circle (black in this case) */
|
background-color: #222; /* Set the color of the circle (black in this case) */
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
.frame.motion {
|
||||||
|
background-color: #7a00b3;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.frame.motion:hover, .frame.motion.active {
|
||||||
|
background-color: #530379;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
/* :nth-child(1 of .frame.motion) {
|
||||||
|
background-color: blue;
|
||||||
|
}
|
||||||
|
:nth-last-child(1 of .frame.motion) {
|
||||||
|
background-color: red;
|
||||||
|
} */
|
||||||
|
|
||||||
.frame-highlight {
|
.frame-highlight {
|
||||||
background-color: red;
|
background-color: red;
|
||||||
width: 25px;
|
width: 25px;
|
||||||
|
|
|
||||||
24
src/utils.js
24
src/utils.js
|
|
@ -19,4 +19,26 @@ function getMousePositionFraction(event, element) {
|
||||||
return 0; // If neither class is present, return 0 (or handle as needed)
|
return 0; // If neither class is present, return 0 (or handle as needed)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { titleCase, getMousePositionFraction };
|
function getKeyframesSurrounding(frames, index) {
|
||||||
|
let lastKeyframeBefore = undefined;
|
||||||
|
let firstKeyframeAfter = undefined;
|
||||||
|
|
||||||
|
// Find the last keyframe before the given index
|
||||||
|
for (let i = index - 1; i >= 0; i--) {
|
||||||
|
if (frames[i].frameType === "keyframe") {
|
||||||
|
lastKeyframeBefore = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the first keyframe after the given index
|
||||||
|
for (let i = index + 1; i < frames.length; i++) {
|
||||||
|
if (frames[i].frameType === "keyframe") {
|
||||||
|
firstKeyframeAfter = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { lastKeyframeBefore, firstKeyframeAfter };
|
||||||
|
}
|
||||||
|
|
||||||
|
export { titleCase, getMousePositionFraction, getKeyframesSurrounding };
|
||||||
Loading…
Reference in New Issue