diff --git a/packages/app/src/App.vue b/packages/app/src/App.vue index 0ded0ae..983d8be 100644 --- a/packages/app/src/App.vue +++ b/packages/app/src/App.vue @@ -11,9 +11,9 @@ import Widgets from "./components/Widgets.vue";
- -
-
+ +
+
diff --git a/packages/app/src/components/Preview.vue b/packages/app/src/components/Preview.vue index 511acec..7b0c4f3 100644 --- a/packages/app/src/components/Preview.vue +++ b/packages/app/src/components/Preview.vue @@ -1,28 +1,53 @@ diff --git a/packages/app/src/components/Timeline.vue b/packages/app/src/components/Timeline.vue index aaf616d..19f12ff 100644 --- a/packages/app/src/components/Timeline.vue +++ b/packages/app/src/components/Timeline.vue @@ -1,23 +1,118 @@ + + diff --git a/packages/app/src/components/__test__/TestAnimation.vue b/packages/app/src/components/__test__/TestAnimation.vue index 8db92d8..4bb8ce8 100644 --- a/packages/app/src/components/__test__/TestAnimation.vue +++ b/packages/app/src/components/__test__/TestAnimation.vue @@ -1,12 +1,24 @@ diff --git a/packages/app/src/main.ts b/packages/app/src/main.ts index 742fcc2..9b552b1 100644 --- a/packages/app/src/main.ts +++ b/packages/app/src/main.ts @@ -3,13 +3,20 @@ import "./output.css"; import App from "./App.vue"; import Preview from "./components/Preview.vue"; import "font-awesome/css/font-awesome.css"; +import { createPlayer } from "@vue-motion/core"; // /**@ts-ignore */ // import router from 'virtual:router' // /**@ts-ignore */ // import player from 'virtual:player' if (__D__) { - createApp(App).mount("#app"); + createApp(App) + .use( + createPlayer({ + studio: true, + }), + ) + .mount("#app"); } else { createApp(Preview).mount("#app"); } diff --git a/packages/app/src/output.css b/packages/app/src/output.css index 375fe82..f67a177 100644 --- a/packages/app/src/output.css +++ b/packages/app/src/output.css @@ -554,6 +554,64 @@ video { display: none; } +.\!container { + width: 100% !important; +} + +.container { + width: 100%; +} + +@media (min-width: 640px) { + .\!container { + max-width: 640px !important; + } + + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .\!container { + max-width: 768px !important; + } + + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .\!container { + max-width: 1024px !important; + } + + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .\!container { + max-width: 1280px !important; + } + + .container { + max-width: 1280px; + } +} + +@media (min-width: 1536px) { + .\!container { + max-width: 1536px !important; + } + + .container { + max-width: 1536px; + } +} + .absolute { position: absolute; } @@ -595,8 +653,28 @@ video { height: 2rem; } -.h-\[60\%\] { - height: 60%; +.h-\[100px\] { + height: 100px; +} + +.h-\[40\%\] { + height: 40%; +} + +.h-\[5\%\] { + height: 5%; +} + +.h-\[55\%\] { + height: 55%; +} + +.h-\[60vh\] { + height: 60vh; +} + +.h-\[80px\] { + height: 80px; } .h-full { @@ -607,6 +685,18 @@ video { height: 100vh; } +.h-\[300px\] { + height: 300px; +} + +.max-h-full { + max-height: 100%; +} + +.min-h-0 { + min-height: 0px; +} + .w-1\/4 { width: 25%; } @@ -635,6 +725,14 @@ video { width: 100vw; } +.max-w-full { + max-width: 100%; +} + +.flex-1 { + flex: 1 1 0%; +} + .flex-shrink-0 { flex-shrink: 0; } @@ -643,10 +741,18 @@ video { flex-grow: 1; } +.transform { + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + .cursor-pointer { cursor: pointer; } +.cursor-col-resize { + cursor: col-resize; +} + .select-none { -webkit-user-select: none; -moz-user-select: none; @@ -685,10 +791,18 @@ video { overflow: scroll; } +.overflow-x-auto { + overflow-x: auto; +} + .overflow-y-auto { overflow-y: auto; } +.overflow-y-hidden { + overflow-y: hidden; +} + .overflow-y-visible { overflow-y: visible; } @@ -705,15 +819,37 @@ video { border-bottom-width: 1px; } +.border-r { + border-right-width: 1px; +} + +.border-t { + border-top-width: 1px; +} + +.border-black { + --tw-border-opacity: 1; + border-color: rgb(0 0 0 / var(--tw-border-opacity, 1)); +} + .bg-white { --tw-bg-opacity: 1; background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)); } +.bg-\[\#1f2428\] { + --tw-bg-opacity: 1; + background-color: rgb(31 36 40 / var(--tw-bg-opacity, 1)); +} + .p-1 { padding: 0.25rem; } +.p-10 { + padding: 2.5rem; +} + .p-2 { padding: 0.5rem; } @@ -722,6 +858,10 @@ video { padding: 0.75rem; } +.p-4 { + padding: 1rem; +} + .pl-4 { padding-left: 1rem; } @@ -740,6 +880,11 @@ video { color: rgb(53 73 94 / var(--tw-text-opacity, 1)); } +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity, 1)); +} + .transition-all { transition-property: all; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); diff --git a/packages/core/src/animation.ts b/packages/core/src/animation.ts index 019a8f1..1479ef8 100644 --- a/packages/core/src/animation.ts +++ b/packages/core/src/animation.ts @@ -21,6 +21,12 @@ export interface AnimationInstance { export class AnimationManager { animations: AnimationInstance[] = []; + repeatStatus: [ + boolean, + number | "loop", + number, + AnimationInstance[], + ] = [false, 0, 0, []]; private target: T; constructor(target: T, elapsed: WatchSource) { @@ -62,8 +68,12 @@ export class AnimationManager { context ??= {} as any; const duration = context.duration ?? 1; const by = context.by ?? linear; - this.animations.push({ context, animation, duration, by }); + if (this.repeatStatus[0]) { + // 在重复模式中,将动画保存到重复区间 + this.repeatStatus[3].push({ context, animation, duration, by }); + } + this.animations.push({ context, animation, duration, by }); return this; } @@ -74,14 +84,12 @@ export class AnimationManager { } delay(duration: number) { - this.animate( + return this.animate( () => { /* empty animation */ }, { duration }, ); - - return this; } parallel( @@ -135,13 +143,42 @@ export class AnimationManager { } exec(fn: () => void) { - this.animate( + return this.animate( defineAnimation(() => (progress) => { if (progress === 0) fn(); }), { duration: 0 }, ); } + + repeat(times?: number) { + this.repeatStatus = [true, times ?? "loop", 0, []]; + return this; + } + + repeatEnd(fn?: () => void) { + // 当前重复次数加1 + this.repeatStatus[2] += 1; + + if ( + this.repeatStatus[1] === "loop" || + this.repeatStatus[2] < this.repeatStatus[1] + ) { + // 如果是无限循环或者还没达到总重复次数,将重复区间的动画再次添加到队列 + this.animations.push(...this.repeatStatus[3]); + + if (this.repeatStatus[1] === "loop") { + // 如果是无限循环,重置当前重复次数 + this.repeatStatus[2] = 0; + } + } else { + // 重复完成 + this.repeatStatus[0] = false; + this.repeatStatus[3] = []; + fn?.(); + } + return this; + } } export type AnimationSetup = ( diff --git a/packages/lib/src/widgets/widget.ts b/packages/lib/src/widgets/widget.ts index 2990fee..1999790 100644 --- a/packages/lib/src/widgets/widget.ts +++ b/packages/lib/src/widgets/widget.ts @@ -83,6 +83,8 @@ export type WidgetIns = WidgetOptions & once: (animation: (target: Widget) => void) => void; delay: (duration: number) => void; exec: (fn: () => void) => void; + repeat: (times?: number) => void; + repeatEnd: (fn?: () => void) => void; }; export interface Animatable { animate: ( @@ -462,6 +464,24 @@ export function widget(options: WidgetOptions) { }, options, ); + registerAnimation( + "repeat", + (times: number) => { + return (manager: AnimationManager) => { + return manager.repeat(times); + }; + }, + options, + ); + registerAnimation( + "repeatEnd", + (fn: () => void) => { + return (manager: AnimationManager) => { + return manager.repeatEnd(fn); + }; + }, + options, + ); const animations = inject<(() => Animation)[]>( "ADDITION_ANIMATIONS", ); diff --git a/test/src/App.vue b/test/src/App.vue index f369ea2..d588a15 100644 --- a/test/src/App.vue +++ b/test/src/App.vue @@ -1,6 +1,6 @@