mirror of
https://github.com/OpenVGLab/OmniLottie.git
synced 2026-09-16 23:26:26 +00:00
1001 lines
32 KiB
HTML
1001 lines
32 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en" data-theme="dark">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="description" content="OmniLottie: Generating Vector Animations via Parameterized Lottie Tokens">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>OmniLottie: Generating Vector Animations via Parameterized Lottie Tokens</title>
|
||
<link rel="icon" href="./static/images/lottie_icon.png">
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
|
||
<style>
|
||
:root {
|
||
--bg: #070913;
|
||
--bg-soft: #101526;
|
||
--panel: rgba(21, 28, 52, 0.7);
|
||
--panel-strong: rgba(14, 18, 34, 0.9);
|
||
--text: #eef3ff;
|
||
--sub: #a8b6da;
|
||
--line: rgba(148, 170, 255, 0.22);
|
||
--primary: #9f7bff;
|
||
--secondary: #37d5ff;
|
||
--pink: #ff7ad9;
|
||
--gold: #ffd37f;
|
||
--shadow: 0 28px 52px rgba(0, 0, 0, 0.42);
|
||
--chip: rgba(155, 129, 255, 0.18);
|
||
--btn-text: #0d1329;
|
||
--button-bg: linear-gradient(135deg, #b194ff, #62e6ff);
|
||
--card-bg: linear-gradient(140deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.02));
|
||
--edge-mask: rgba(7, 9, 19, 1);
|
||
}
|
||
|
||
html[data-theme="light"] {
|
||
--bg: #edf2ff;
|
||
--bg-soft: #ffffff;
|
||
--panel: rgba(255, 255, 255, 0.72);
|
||
--panel-strong: rgba(255, 255, 255, 0.88);
|
||
--text: #1a2444;
|
||
--sub: #5a668b;
|
||
--line: rgba(111, 131, 223, 0.24);
|
||
--primary: #7d55ff;
|
||
--secondary: #00c9eb;
|
||
--pink: #ff5fc8;
|
||
--gold: #ffb546;
|
||
--shadow: 0 24px 42px rgba(100, 120, 186, 0.22);
|
||
--chip: rgba(126, 86, 255, 0.12);
|
||
--btn-text: #ffffff;
|
||
--button-bg: linear-gradient(135deg, #9068ff, #00c8eb);
|
||
--card-bg: linear-gradient(140deg, rgba(255, 255, 255, 0.95), rgba(239, 246, 255, 0.68));
|
||
--edge-mask: rgba(237, 242, 255, 1);
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
|
||
html, body { width: 100%; }
|
||
|
||
body {
|
||
margin: 0;
|
||
font-family: Inter, system-ui, -apple-system, Segoe UI, sans-serif;
|
||
color: var(--text);
|
||
min-height: 100vh;
|
||
background:
|
||
radial-gradient(900px 700px at 8% -10%, rgba(192, 120, 255, 0.32), transparent 55%),
|
||
radial-gradient(1000px 740px at 95% -12%, rgba(61, 216, 255, 0.28), transparent 58%),
|
||
radial-gradient(860px 620px at 55% 115%, rgba(255, 124, 205, 0.22), transparent 65%),
|
||
linear-gradient(160deg, var(--bg), var(--bg-soft));
|
||
transition: background 0.35s ease, color 0.25s ease;
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
.bg-grid {
|
||
position: fixed;
|
||
inset: 0;
|
||
pointer-events: none;
|
||
z-index: 0;
|
||
background-image:
|
||
linear-gradient(rgba(130, 150, 255, 0.08) 1px, transparent 1px),
|
||
linear-gradient(90deg, rgba(130, 150, 255, 0.08) 1px, transparent 1px);
|
||
background-size: 42px 42px;
|
||
mask-image: radial-gradient(circle at 50% 50%, rgba(0, 0, 0, 0.88), transparent 78%);
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.aurora {
|
||
position: fixed;
|
||
border-radius: 999px;
|
||
filter: blur(44px);
|
||
pointer-events: none;
|
||
z-index: 0;
|
||
opacity: 0.44;
|
||
animation: drift 11s ease-in-out infinite;
|
||
}
|
||
|
||
.aurora.a1 {
|
||
width: 360px;
|
||
height: 360px;
|
||
left: -90px;
|
||
top: 40px;
|
||
background: rgba(175, 134, 255, 0.9);
|
||
}
|
||
|
||
.aurora.a2 {
|
||
width: 300px;
|
||
height: 300px;
|
||
right: -60px;
|
||
top: 180px;
|
||
background: rgba(81, 218, 255, 0.86);
|
||
animation-delay: -4s;
|
||
}
|
||
|
||
.aurora.a3 {
|
||
width: 340px;
|
||
height: 340px;
|
||
left: 40%;
|
||
bottom: -110px;
|
||
background: rgba(255, 126, 203, 0.74);
|
||
animation-delay: -7s;
|
||
}
|
||
|
||
@keyframes drift {
|
||
0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
|
||
50% { transform: translate3d(0, -22px, 0) scale(1.05); }
|
||
}
|
||
|
||
.shell {
|
||
position: relative;
|
||
z-index: 1;
|
||
width: min(1280px, calc(100% - 28px));
|
||
margin: 18px auto 28px;
|
||
display: grid;
|
||
gap: 16px;
|
||
}
|
||
|
||
.panel {
|
||
position: relative;
|
||
border-radius: 28px;
|
||
border: 1px solid var(--line);
|
||
background: var(--panel);
|
||
backdrop-filter: blur(16px);
|
||
-webkit-backdrop-filter: blur(16px);
|
||
box-shadow: var(--shadow);
|
||
padding: 24px;
|
||
overflow: hidden;
|
||
isolation: isolate;
|
||
}
|
||
|
||
.panel::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: -40% 50% auto -20%;
|
||
height: 80%;
|
||
transform: rotate(-8deg);
|
||
background: linear-gradient(120deg, rgba(159, 123, 255, 0.16), rgba(55, 213, 255, 0.08), transparent);
|
||
z-index: -1;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.hero {
|
||
text-align: center;
|
||
padding-top: 18px;
|
||
padding-bottom: 26px;
|
||
background: linear-gradient(150deg, var(--panel), rgba(255, 255, 255, 0.02));
|
||
}
|
||
|
||
.hero-toolbar {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.tag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 999px;
|
||
background: rgba(255, 255, 255, 0.06);
|
||
color: var(--sub);
|
||
padding: 7px 14px;
|
||
font-weight: 600;
|
||
font-size: 0.85rem;
|
||
}
|
||
|
||
.theme-toggle {
|
||
border: none;
|
||
border-radius: 999px;
|
||
background: var(--button-bg);
|
||
color: var(--btn-text);
|
||
padding: 8px 16px;
|
||
font-size: 0.86rem;
|
||
font-weight: 800;
|
||
cursor: pointer;
|
||
transition: transform 0.2s ease, filter 0.2s ease;
|
||
box-shadow: 0 8px 24px rgba(87, 175, 255, 0.24);
|
||
}
|
||
|
||
.theme-toggle:hover {
|
||
transform: translateY(-2px);
|
||
filter: saturate(1.1);
|
||
}
|
||
|
||
.hero-title {
|
||
margin: 6px auto 8px;
|
||
max-width: 940px;
|
||
font-size: clamp(1.42rem, 1.08rem + 1.74vw, 2.34rem);
|
||
line-height: 1.2;
|
||
letter-spacing: 0.01em;
|
||
text-wrap: balance;
|
||
background: linear-gradient(120deg, #ffffff 15%, #bdd6ff 38%, #a695ff 58%, #74e8ff 82%, #ffc0ea);
|
||
-webkit-background-clip: text;
|
||
background-clip: text;
|
||
color: transparent;
|
||
}
|
||
|
||
html[data-theme="light"] .hero-title {
|
||
background: linear-gradient(120deg, #0f1b44 12%, #2f3f87 34%, #7b53ff 60%, #0086a8 84%);
|
||
-webkit-background-clip: text;
|
||
background-clip: text;
|
||
}
|
||
|
||
.hero-sub {
|
||
margin: 0 auto;
|
||
max-width: 760px;
|
||
color: var(--sub);
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.authors, .meta-row, .links {
|
||
margin-top: 14px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
}
|
||
|
||
.author-chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
text-decoration: none;
|
||
color: var(--text);
|
||
border: 1px solid var(--line);
|
||
background: var(--chip);
|
||
border-radius: 999px;
|
||
padding: 8px 12px;
|
||
font-size: 0.9rem;
|
||
transition: transform 0.2s ease, border-color 0.2s ease;
|
||
}
|
||
|
||
.author-chip:hover {
|
||
transform: translateY(-2px);
|
||
border-color: rgba(157, 188, 255, 0.48);
|
||
}
|
||
|
||
.meta-row {
|
||
color: var(--sub);
|
||
font-size: 0.93rem;
|
||
gap: 8px 14px;
|
||
}
|
||
|
||
.links { gap: 10px 12px; }
|
||
|
||
.link-pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
text-decoration: none;
|
||
color: var(--text);
|
||
font-weight: 700;
|
||
font-size: 0.93rem;
|
||
border: 1px solid var(--line);
|
||
border-radius: 12px;
|
||
padding: 9px 13px;
|
||
background: linear-gradient(140deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.02));
|
||
transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
|
||
}
|
||
|
||
.link-pill:hover {
|
||
transform: translateY(-2px);
|
||
border-color: rgba(157, 188, 255, 0.48);
|
||
box-shadow: 0 10px 22px rgba(93, 98, 206, 0.24);
|
||
}
|
||
|
||
.section-head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
gap: 10px;
|
||
margin-bottom: 12px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.section-head h2 {
|
||
margin: 0;
|
||
font-size: clamp(1.06rem, 0.93rem + 0.96vw, 1.6rem);
|
||
letter-spacing: 0.01em;
|
||
}
|
||
|
||
.section-head p {
|
||
margin: 0;
|
||
color: var(--sub);
|
||
font-size: 0.93rem;
|
||
}
|
||
|
||
.demo-stack {
|
||
display: grid;
|
||
gap: 14px;
|
||
}
|
||
|
||
.video-shell {
|
||
border-radius: 20px;
|
||
border: 1px solid var(--line);
|
||
background: var(--panel-strong);
|
||
padding: 12px;
|
||
}
|
||
|
||
.video-chip {
|
||
display: inline-flex;
|
||
margin-bottom: 10px;
|
||
border-radius: 999px;
|
||
border: 1px solid rgba(164, 151, 255, 0.34);
|
||
background: rgba(167, 139, 250, 0.12);
|
||
color: var(--sub);
|
||
padding: 5px 10px;
|
||
font-size: 0.79rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.03em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.video-frame {
|
||
border-radius: 14px;
|
||
overflow: hidden;
|
||
border: 1px solid rgba(143, 167, 255, 0.2);
|
||
background: #02040d;
|
||
aspect-ratio: 16 / 9;
|
||
min-height: 230px;
|
||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04);
|
||
}
|
||
|
||
.video-frame video {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: block;
|
||
object-fit: contain;
|
||
}
|
||
|
||
.overlay-player {
|
||
position: relative;
|
||
border-radius: 14px;
|
||
overflow: hidden;
|
||
border: 1px solid rgba(143, 167, 255, 0.2);
|
||
background: #02040d;
|
||
aspect-ratio: 16 / 9;
|
||
min-height: 230px;
|
||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04);
|
||
}
|
||
|
||
.overlay-video {
|
||
position: absolute;
|
||
inset: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: contain;
|
||
opacity: 0;
|
||
transition: opacity 0.8s ease;
|
||
}
|
||
|
||
.overlay-video.active { opacity: 1; }
|
||
|
||
.overlay-controls {
|
||
margin-top: 11px;
|
||
display: flex;
|
||
justify-content: center;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.overlay-btn {
|
||
border-radius: 999px;
|
||
border: 1px solid var(--line);
|
||
background: rgba(255, 255, 255, 0.09);
|
||
color: var(--text);
|
||
padding: 8px 12px;
|
||
font-size: 0.86rem;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
transition: transform 0.2s ease, border-color 0.2s ease, background 0.2s ease;
|
||
}
|
||
|
||
.overlay-btn.active {
|
||
border-color: rgba(144, 200, 255, 0.62);
|
||
background: linear-gradient(135deg, rgba(159, 123, 255, 0.3), rgba(55, 213, 255, 0.3));
|
||
}
|
||
|
||
.overlay-btn:hover { transform: translateY(-1px); }
|
||
|
||
.status {
|
||
color: var(--sub);
|
||
font-size: 0.92rem;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.lottie-rows {
|
||
display: grid;
|
||
gap: 10px;
|
||
mask-image: linear-gradient(to right, transparent 0, #000 6%, #000 94%, transparent 100%);
|
||
-webkit-mask-image: linear-gradient(to right, transparent 0, #000 6%, #000 94%, transparent 100%);
|
||
}
|
||
|
||
.lottie-lane {
|
||
overflow: hidden;
|
||
border-radius: 16px;
|
||
border: 1px solid var(--line);
|
||
background: var(--card-bg);
|
||
padding: 6px 0;
|
||
}
|
||
|
||
.lane-track {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: stretch;
|
||
will-change: transform;
|
||
padding: 2px 8px;
|
||
}
|
||
|
||
.lottie-card {
|
||
width: clamp(120px, 12.2vw, 178px);
|
||
border-radius: 14px;
|
||
border: 1px solid rgba(141, 163, 255, 0.24);
|
||
background: linear-gradient(140deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.03));
|
||
flex: 0 0 auto;
|
||
padding: 8px;
|
||
display: grid;
|
||
gap: 6px;
|
||
transition: transform 0.2s ease, border-color 0.2s ease;
|
||
}
|
||
|
||
.lottie-card:hover {
|
||
transform: translateY(-2px) scale(1.01);
|
||
border-color: rgba(157, 188, 255, 0.6);
|
||
}
|
||
|
||
.lottie-canvas {
|
||
height: clamp(70px, 7.8vw, 118px);
|
||
border-radius: 10px;
|
||
overflow: hidden;
|
||
background: rgba(8, 11, 22, 0.65);
|
||
border: 1px solid rgba(141, 163, 255, 0.2);
|
||
}
|
||
|
||
html[data-theme="light"] .lottie-canvas {
|
||
background: rgba(245, 249, 255, 0.9);
|
||
}
|
||
|
||
.lottie-name {
|
||
color: var(--sub);
|
||
font-size: 0.72rem;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.tech-grid {
|
||
display: grid;
|
||
gap: 12px;
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.tech-card {
|
||
border-radius: 18px;
|
||
border: 1px solid var(--line);
|
||
background: var(--card-bg);
|
||
padding: 14px;
|
||
}
|
||
|
||
.tech-card h3 {
|
||
margin: 0;
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.tech-card p {
|
||
margin: 8px 0 10px;
|
||
color: var(--sub);
|
||
line-height: 1.7;
|
||
font-size: 0.93rem;
|
||
}
|
||
|
||
.tech-card img {
|
||
width: 100%;
|
||
border-radius: 12px;
|
||
border: 1px solid rgba(141, 163, 255, 0.18);
|
||
display: block;
|
||
background: rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.citation-box {
|
||
border-radius: 16px;
|
||
border: 1px solid var(--line);
|
||
background: rgba(4, 9, 24, 0.76);
|
||
padding: 12px;
|
||
overflow: auto;
|
||
}
|
||
|
||
html[data-theme="light"] .citation-box {
|
||
background: rgba(250, 252, 255, 0.9);
|
||
}
|
||
|
||
.citation-box pre {
|
||
margin: 0;
|
||
color: var(--sub);
|
||
line-height: 1.6;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
font-size: 0.87rem;
|
||
}
|
||
|
||
.citation-actions {
|
||
margin-top: 10px;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.copy-btn {
|
||
border: 1px solid var(--line);
|
||
border-radius: 999px;
|
||
background: rgba(255, 255, 255, 0.1);
|
||
color: var(--text);
|
||
font-weight: 700;
|
||
padding: 7px 12px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.footer {
|
||
text-align: center;
|
||
color: var(--sub);
|
||
font-size: 0.86rem;
|
||
padding-bottom: 3px;
|
||
}
|
||
|
||
@media (max-width: 980px) {
|
||
.panel { padding: 18px; border-radius: 22px; }
|
||
.video-frame, .overlay-player { min-height: 190px; }
|
||
.tech-grid { grid-template-columns: 1fr; }
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.shell { width: calc(100% - 16px); }
|
||
.hero-title { font-size: clamp(1.25rem, 1.05rem + 1.75vw, 1.84rem); }
|
||
.video-frame, .overlay-player { min-height: 150px; }
|
||
.lottie-card { width: clamp(116px, 35vw, 150px); }
|
||
.lottie-canvas { height: clamp(66px, 20vw, 95px); }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="bg-grid"></div>
|
||
<div class="aurora a1"></div>
|
||
<div class="aurora a2"></div>
|
||
<div class="aurora a3"></div>
|
||
|
||
<main class="shell">
|
||
<section class="panel hero">
|
||
<div class="hero-toolbar">
|
||
<span class="tag" style="font-size:1.20em; display:inline-flex; align-items:center; gap:0.28em;">
|
||
<span style="font-weight:800;">CVPR 2026</span>
|
||
</span>
|
||
<button id="themeToggle" class="theme-toggle" type="button" style="margin-left:auto;">Bright Mode</button>
|
||
</div>
|
||
|
||
<h1 class="hero-title">OmniLottie: Generating Vector Animations via Parameterized Lottie Tokens</h1>
|
||
<p class="hero-sub">
|
||
An open-source work by OpenVGLab
|
||
<a href="https://github.com/OpenVGLab" target="_blank" rel="noreferrer"
|
||
style="display:inline-block; margin-left:0.75em; vertical-align:middle;">
|
||
<img src="https://img.shields.io/github/followers/OpenVGLab?label=Follow%20%40OpenVGLab&style=social" alt="Follow @OpenVGLab on GitHub" style="height:1.2em;vertical-align:-0.18em;">
|
||
</a>
|
||
</p>
|
||
|
||
<div class="authors">
|
||
<a class="author-chip" href="https://github.com/YiyingYang12/YiyingYang12" target="_blank" rel="noreferrer">Yiying Yang<sup>1,2</sup></a>
|
||
<a class="author-chip" href="https://wchengad.github.io" target="_blank" rel="noreferrer">Wei Cheng<sup>2†</sup></a>
|
||
<a class="author-chip" href="https://ch3cook-fdu.github.io/" target="_blank" rel="noreferrer">Sijin Chen<sup>3</sup></a>
|
||
<a class="author-chip" href="https://romgai.github.io/" target="_blank" rel="noreferrer">Honghao Fu<sup>2,4</sup></a>
|
||
<a class="author-chip" href="https://scholar.google.com/citations?user=tgDc0fsAAAAJ&hl=en" target="_blank" rel="noreferrer">XianFang Zeng<sup>2</sup></a>
|
||
<a class="author-chip" href="https://vanoracai.github.io/" target="_blank" rel="noreferrer">Yujun Cai<sup>4</sup></a>
|
||
<a class="author-chip" href="https://www.skicyyu.org/" target="_blank" rel="noreferrer">Gang Yu<sup>2‡</sup></a>
|
||
<a class="author-chip" href="http://xingjunma.com/" target="_blank" rel="noreferrer">Xinjun Ma<sup>1‡</sup></a>
|
||
</div>
|
||
|
||
<div class="meta-row">
|
||
<span><sup>1</sup>Fudan University</span>
|
||
<span><sup>2</sup>StepFun</span>
|
||
<span><sup>3</sup>HKU MMLab</span>
|
||
<span><sup>4</sup>University of Queensland</span>
|
||
</div>
|
||
<div class="meta-row">
|
||
<span><sup>†</sup>Project Lead</span>
|
||
<span><sup>‡</sup>Correspondence Authors</span>
|
||
</div>
|
||
|
||
<div class="links">
|
||
<a class="link-pill" href="https://arxiv.org" target="_blank" rel="noreferrer"><i class="fa-solid fa-file-lines"></i>arXiv</a>
|
||
<a class="link-pill" href="https://github.com/OpenVGLab/OmniLottie" target="_blank" rel="noreferrer"><i class="fa-brands fa-github"></i>Code</a>
|
||
<a class="link-pill" href="https://huggingface.co/OmniLottie/OmniLottie" target="_blank" rel="noreferrer">
|
||
<img src="https://huggingface.co/front/assets/huggingface_logo-noborder.svg" alt="HuggingFace" style="height:1.05em;vertical-align:-0.10em;margin-right:-0.09em;">Model
|
||
</a>
|
||
<a class="link-pill" href="https://huggingface.co/datasets/OmniLottie/MMLottie-2M" target="_blank" rel="noreferrer">
|
||
<img src="https://huggingface.co/front/assets/huggingface_logo-noborder.svg" alt="HuggingFace" style="height:1.05em;vertical-align:-0.10em;margin-right:-0.09em;">MMLottie-2M
|
||
</a>
|
||
<a class="link-pill" href="https://huggingface.co/datasets/OmniLottie/MMLottieBench" target="_blank" rel="noreferrer">
|
||
<img src="https://huggingface.co/front/assets/huggingface_logo-noborder.svg" alt="HuggingFace" style="height:1.05em;vertical-align:-0.10em;margin-right:-0.09em;">MMLottie-Bench
|
||
</a>
|
||
<a class="link-pill" href="https://huggingface.co/datasets/OmniLottie/MMLottie-2M" target="_blank" rel="noreferrer">
|
||
<img src="https://huggingface.co/front/assets/huggingface_logo-noborder.svg" alt="HuggingFace" style="height:1.05em;vertical-align:-0.10em;margin-right:-0.09em;">Demo
|
||
</a>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="panel">
|
||
<div class="section-head">
|
||
<h2>Lottie Parametrization and Generation Process</h2>
|
||
<!-- <p>上方主 Demo + 下方 3 个叠加切换 Demo</p> -->
|
||
</div>
|
||
|
||
<div class="demo-stack">
|
||
<article class="video-shell">
|
||
<!-- <div class="video-chip">Lottie Parametrization and Generation Process</div> -->
|
||
<div class="video-frame">
|
||
<video src="./assets/OmniLottie-main-demo.mp4" autoplay muted loop playsinline></video>
|
||
</div>
|
||
</article>
|
||
|
||
<article class="video-shell">
|
||
<!-- <div class="video-chip">Overlay Demos</div> -->
|
||
<div class="overlay-player" id="overlayPlayer">
|
||
<video class="overlay-video active" src="./assets/OmniLottie-demo-1.mp4" autoplay muted loop playsinline></video>
|
||
<video class="overlay-video" src="./assets/OmniLottie-demo-2.mp4" autoplay muted loop playsinline></video>
|
||
<video class="overlay-video" src="./assets/OmniLottie-demo-3.mp4" autoplay muted loop playsinline></video>
|
||
</div>
|
||
<div class="overlay-controls" id="overlayControls">
|
||
<button class="overlay-btn active" type="button" data-index="0">Demo 1</button>
|
||
<button class="overlay-btn" type="button" data-index="1">Demo 2</button>
|
||
<button class="overlay-btn" type="button" data-index="2">Demo 3</button>
|
||
</div>
|
||
</article>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="panel">
|
||
<div class="section-head">
|
||
<h2>Generated Lottie Gallery</h2>
|
||
</div>
|
||
<div id="lottieStatus" class="status">Loading...</div>
|
||
<div id="lottieRows" class="lottie-rows"></div>
|
||
</section>
|
||
|
||
<section class="panel">
|
||
<div class="section-head">
|
||
<h2>Introducing OmniLottie</h2>
|
||
<!-- <p>Dataset(Image #3)与技术路径(Image #2)</p> -->
|
||
</div>
|
||
|
||
<div class="tech-grid">
|
||
<article class="tech-card">
|
||
<h3>MMLottie-2M Dataset</h3>
|
||
<!-- <p>MMLottie-2M 汇集大规模专业 Lottie 动画及多模态标注,覆盖文本、关键帧和视频渲染,支持从语义到细节动作的学习。</p> -->
|
||
<p><b>Overview of the Vector Animation Data Construction Pipeline.</b>
|
||
<b>Data Collection & Conversion :</b> We convert SVG assets into static Lottie files and apply randomized animation effects to generate effect-tagged animated Lotties. In parallel, we gather professionally created Lottie animations from five online platforms and perform thorough filtering and cleaning. <b>Data Processing :</b> Each animation undergoes spatio-temporal normalization, followed by video rendering and random keyframe extraction. <b>Data Annotation :</b> Finally, we provide multi-granularity annotations emphasizing geometric structure, color attributes, and motion characteristics.</p>
|
||
<img src="./assets/OmniLottie-data-pipeline.png" alt="Dataset and data processing pipeline">
|
||
</article>
|
||
|
||
<article class="tech-card">
|
||
<h3>OmniLottie Model</h3>
|
||
<p><b>Overview of OmniLottie.</b>
|
||
<b>Lottie Structure :</b> We reorganize the Lottie JSON representation, with a particular focus on the structure of its layers, including both common layer attributes and five special layer types. <b>Lottie Parametrization :</b> The hierarchical JSON format of Lottie is flattened into a sequence of function calls, which are further parameterized to define a dedicated vocabulary and token set for Lottie. <b>OmniLottie :</b> Built upon this parameterization, OmniLottie extends Qwen2.5-VL with a new tokenizer and vocabulary for Lottie, and is trained on our curated Lottie dataset.</p>
|
||
<img src="./assets/OmniLottie-pipeline.png" alt="Technical pipeline">
|
||
</article>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="panel">
|
||
<div class="section-head">
|
||
<h2>Citation</h2>
|
||
</div>
|
||
|
||
<div class="citation-box">
|
||
<pre id="citationText">@article{omnilottie2026,
|
||
title={OmniLottie: Generating Vector Animations via Parameterized Lottie Tokens},
|
||
author={Yang, Yiying and Cheng, Wei and Chen, Sijin and Fu, Honghao and Zeng, XianFang and Cai, Yujun and Yu, Gang and Ma, Xinjun},
|
||
journal={In Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)},
|
||
year={2026}
|
||
}</pre>
|
||
</div>
|
||
<div class="citation-actions">
|
||
<button id="copyCitation" class="copy-btn" type="button">Copy Citation</button>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="footer">OpenVGLab/OmniLottie</div>
|
||
</main>
|
||
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bodymovin/5.12.2/lottie.min.js"></script>
|
||
<script>
|
||
const THEME_KEY = 'omnilottie-theme';
|
||
const laneStates = [];
|
||
let overlayTimer = null;
|
||
let lanesLoopStarted = false;
|
||
|
||
function applyTheme(theme) {
|
||
const mode = theme === 'light' ? 'light' : 'dark';
|
||
document.documentElement.setAttribute('data-theme', mode);
|
||
localStorage.setItem(THEME_KEY, mode);
|
||
const btn = document.getElementById('themeToggle');
|
||
if (btn) {
|
||
btn.textContent = mode === 'dark' ? 'Bright Mode' : 'Dark Mode';
|
||
}
|
||
}
|
||
|
||
function initTheme() {
|
||
const cached = localStorage.getItem(THEME_KEY);
|
||
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||
applyTheme(cached || (prefersDark ? 'dark' : 'light'));
|
||
|
||
const btn = document.getElementById('themeToggle');
|
||
if (!btn) return;
|
||
btn.addEventListener('click', () => {
|
||
const current = document.documentElement.getAttribute('data-theme');
|
||
applyTheme(current === 'dark' ? 'light' : 'dark');
|
||
});
|
||
}
|
||
|
||
function initOverlayPlayer() {
|
||
const videos = [...document.querySelectorAll('#overlayPlayer .overlay-video')];
|
||
const buttons = [...document.querySelectorAll('#overlayControls .overlay-btn')];
|
||
if (!videos.length || !buttons.length) return;
|
||
|
||
const switchTo = (index, fromStart = false) => {
|
||
videos.forEach((video, i) => {
|
||
const active = i === index;
|
||
video.classList.toggle('active', active);
|
||
if (active) {
|
||
if (fromStart) video.currentTime = 0;
|
||
video.play().catch(() => {});
|
||
}
|
||
});
|
||
buttons.forEach((button, i) => button.classList.toggle('active', i === index));
|
||
};
|
||
|
||
let current = 0;
|
||
|
||
function restartTimer(startIndex) {
|
||
if (typeof startIndex === 'number') current = startIndex;
|
||
if (overlayTimer) clearInterval(overlayTimer);
|
||
overlayTimer = setInterval(() => {
|
||
current = (current + 1) % videos.length;
|
||
switchTo(current);
|
||
}, 10000);
|
||
}
|
||
|
||
buttons.forEach((button) => {
|
||
button.addEventListener('click', () => {
|
||
const idx = Number(button.dataset.index || 0);
|
||
switchTo(idx, true);
|
||
restartTimer(idx);
|
||
});
|
||
});
|
||
|
||
switchTo(0);
|
||
restartTimer(0);
|
||
}
|
||
|
||
function prettyName(file) {
|
||
return file
|
||
.replace('.json', '')
|
||
.replace(/_/g, ' ')
|
||
.replace(/\s+/g, ' ')
|
||
.trim();
|
||
}
|
||
|
||
function normalizeJsonName(input) {
|
||
const noHash = String(input || '').split('#')[0];
|
||
const noQuery = noHash.split('?')[0];
|
||
const rawName = noQuery.split('/').pop() || '';
|
||
try {
|
||
return decodeURIComponent(rawName);
|
||
} catch (_) {
|
||
return rawName;
|
||
}
|
||
}
|
||
|
||
async function loadManifestFiles(manifestPath) {
|
||
try {
|
||
const response = await fetch(manifestPath, { cache: 'no-store' });
|
||
if (!response.ok) return [];
|
||
const data = await response.json();
|
||
if (!Array.isArray(data.files)) return [];
|
||
return data.files
|
||
.map(normalizeJsonName)
|
||
.filter((name) => name.toLowerCase().endsWith('.json') && name.toLowerCase() !== 'manifest.json');
|
||
} catch (_) {
|
||
return [];
|
||
}
|
||
}
|
||
|
||
async function scanDirectoryJsonFiles(baseDir) {
|
||
try {
|
||
const response = await fetch(`${baseDir}/`, { cache: 'no-store' });
|
||
if (!response.ok) return [];
|
||
const html = await response.text();
|
||
const links = [...html.matchAll(/href\s*=\s*["']([^"']+)["']/gi)].map((match) => match[1]);
|
||
return links
|
||
.map(normalizeJsonName)
|
||
.filter((name) => name.toLowerCase().endsWith('.json') && name.toLowerCase() !== 'manifest.json');
|
||
} catch (_) {
|
||
return [];
|
||
}
|
||
}
|
||
|
||
function uniqueSorted(files) {
|
||
return [...new Set(files)].sort((a, b) => a.localeCompare(b));
|
||
}
|
||
|
||
async function loadAllJsonFiles() {
|
||
const baseDirs = ['./assets/demo_new_set', './assets/demo_newset'];
|
||
|
||
for (const baseDir of baseDirs) {
|
||
const [directoryFiles, manifestFiles] = await Promise.all([
|
||
scanDirectoryJsonFiles(baseDir),
|
||
loadManifestFiles(`${baseDir}/manifest.json`)
|
||
]);
|
||
|
||
const files = uniqueSorted([...directoryFiles, ...manifestFiles]);
|
||
if (files.length) {
|
||
const source = directoryFiles.length ? 'directory+manifest' : 'manifest';
|
||
return { files, baseDir, source };
|
||
}
|
||
}
|
||
|
||
return { files: [], baseDir: '', source: 'none' };
|
||
}
|
||
|
||
function createCard(file, baseDir, index, laneState) {
|
||
const card = document.createElement('article');
|
||
card.className = 'lottie-card';
|
||
|
||
const canvas = document.createElement('div');
|
||
canvas.className = 'lottie-canvas';
|
||
|
||
card.appendChild(canvas);
|
||
|
||
const animation = window.lottie.loadAnimation({
|
||
container: canvas,
|
||
renderer: 'svg',
|
||
loop: true,
|
||
autoplay: true,
|
||
path: `${baseDir}/${encodeURIComponent(file)}`,
|
||
rendererSettings: {
|
||
progressiveLoad: true,
|
||
preserveAspectRatio: 'xMidYMid meet'
|
||
}
|
||
});
|
||
animation.setSpeed(0.8 + (index % 8) * 0.04);
|
||
|
||
card.addEventListener('mouseenter', () => {
|
||
animation.pause();
|
||
laneState.paused = true;
|
||
});
|
||
|
||
card.addEventListener('mouseleave', () => {
|
||
animation.play();
|
||
laneState.paused = false;
|
||
});
|
||
|
||
return card;
|
||
}
|
||
|
||
function createLane(files, baseDir, laneIndex) {
|
||
const lane = document.createElement('div');
|
||
lane.className = 'lottie-lane';
|
||
|
||
const track = document.createElement('div');
|
||
track.className = 'lane-track';
|
||
lane.appendChild(track);
|
||
|
||
const direction = laneIndex % 2 === 0 ? 'left' : 'right';
|
||
const laneState = {
|
||
lane,
|
||
track,
|
||
laneIndex,
|
||
direction,
|
||
speed: (0.34 + laneIndex * 0.08) * 1.5,
|
||
offset: 0,
|
||
paused: false,
|
||
gap: 10
|
||
};
|
||
|
||
files.forEach((file, idx) => {
|
||
track.appendChild(createCard(file, baseDir, idx, laneState));
|
||
});
|
||
|
||
lane.addEventListener('mouseenter', () => { laneState.paused = true; });
|
||
lane.addEventListener('mouseleave', () => { laneState.paused = false; });
|
||
laneStates.push(laneState);
|
||
|
||
return lane;
|
||
}
|
||
|
||
function tickLanes(timestamp) {
|
||
laneStates.forEach((state) => {
|
||
if (state.paused) return;
|
||
|
||
const cards = state.track.children;
|
||
if (!cards.length) return;
|
||
|
||
state.gap = Number.parseFloat(getComputedStyle(state.track).gap || '10') || 10;
|
||
state.offset += state.speed;
|
||
const wave = Math.sin(timestamp * 0.001 + state.laneIndex) * 2.2;
|
||
|
||
if (state.direction === 'left') {
|
||
let first = state.track.firstElementChild;
|
||
while (first) {
|
||
const width = first.getBoundingClientRect().width + state.gap;
|
||
if (state.offset < width) break;
|
||
state.track.appendChild(first);
|
||
state.offset -= width;
|
||
first = state.track.firstElementChild;
|
||
}
|
||
state.track.style.transform = `translate3d(${-state.offset}px, ${wave}px, 0)`;
|
||
} else {
|
||
let last = state.track.lastElementChild;
|
||
while (last) {
|
||
const width = last.getBoundingClientRect().width + state.gap;
|
||
if (state.offset < 1) break;
|
||
state.track.insertBefore(last, state.track.firstElementChild);
|
||
state.offset -= width;
|
||
last = state.track.lastElementChild;
|
||
}
|
||
state.track.style.transform = `translate3d(${state.offset}px, ${-wave}px, 0)`;
|
||
}
|
||
});
|
||
|
||
requestAnimationFrame(tickLanes);
|
||
}
|
||
|
||
async function initLottieWall() {
|
||
const statusEl = document.getElementById('lottieStatus');
|
||
const rowsEl = document.getElementById('lottieRows');
|
||
if (!statusEl || !rowsEl) return;
|
||
|
||
const { files, baseDir, source } = await loadAllJsonFiles();
|
||
if (!files.length) {
|
||
statusEl.textContent = 'No Lottie files found, please check the assets/demo_new_set directory or manifest.json';
|
||
return;
|
||
}
|
||
|
||
const rows = [[], [], [], [], []];
|
||
files.forEach((file, index) => rows[index % 5].push(file));
|
||
|
||
rows.forEach((rowFiles, laneIndex) => {
|
||
rowsEl.appendChild(createLane(rowFiles, baseDir, laneIndex));
|
||
});
|
||
|
||
statusEl.textContent = `Loaded ${files.length} Lottie files, all generated by OmniLottie`;
|
||
if (!lanesLoopStarted) {
|
||
lanesLoopStarted = true;
|
||
requestAnimationFrame(tickLanes);
|
||
}
|
||
}
|
||
|
||
function initCitationCopy() {
|
||
const btn = document.getElementById('copyCitation');
|
||
const citationEl = document.getElementById('citationText');
|
||
if (!btn || !citationEl) return;
|
||
|
||
btn.addEventListener('click', async () => {
|
||
const text = citationEl.textContent || '';
|
||
try {
|
||
await navigator.clipboard.writeText(text);
|
||
btn.textContent = 'Copied Citation';
|
||
setTimeout(() => { btn.textContent = 'Copied Citation'; }, 1200);
|
||
} catch (_) {
|
||
btn.textContent = 'Copy Citation Failed';
|
||
setTimeout(() => { btn.textContent = 'Copied Citation'; }, 1200);
|
||
}
|
||
});
|
||
}
|
||
|
||
window.addEventListener('DOMContentLoaded', () => {
|
||
initTheme();
|
||
initOverlayPlayer();
|
||
initLottieWall();
|
||
initCitationCopy();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|