2026-02-12 21:31:55 +08:00
<!DOCTYPE html>
2026-02-28 08:48:50 +00:00
< html lang = "en" data-theme = "dark" >
2026-02-12 21:31:55 +08:00
< head >
2026-02-28 08:48:50 +00:00
< 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 28 px 52 px rgba ( 0 , 0 , 0 , 0.42 );
--chip : rgba ( 155 , 129 , 255 , 0.18 );
--btn-text : #0d1329 ;
--button-bg : linear-gradient ( 135 deg , #b194ff , #62e6ff );
--card-bg : linear-gradient ( 140 deg , 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 24 px 42 px rgba ( 100 , 120 , 186 , 0.22 );
--chip : rgba ( 126 , 86 , 255 , 0.12 );
--btn-text : #ffffff ;
--button-bg : linear-gradient ( 135 deg , #9068ff , #00c8eb );
--card-bg : linear-gradient ( 140 deg , 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 : 100 vh ;
background :
radial-gradient ( 900 px 700 px at 8 % -10 % , rgba ( 192 , 120 , 255 , 0.32 ), transparent 55 % ),
radial-gradient ( 1000 px 740 px at 95 % -12 % , rgba ( 61 , 216 , 255 , 0.28 ), transparent 58 % ),
radial-gradient ( 860 px 620 px at 55 % 115 % , rgba ( 255 , 124 , 205 , 0.22 ), transparent 65 % ),
linear-gradient ( 160 deg , var ( -- bg ), var ( -- bg - soft ));
transition : background 0.35 s ease , color 0.25 s 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 ) 1 px , transparent 1 px ),
linear-gradient ( 90 deg , rgba ( 130 , 150 , 255 , 0.08 ) 1 px , transparent 1 px );
background-size : 42 px 42 px ;
mask-image : radial-gradient ( circle at 50 % 50 % , rgba ( 0 , 0 , 0 , 0.88 ), transparent 78 % );
opacity : 0.5 ;
}
. aurora {
position : fixed ;
border-radius : 999 px ;
filter : blur ( 44 px );
pointer-events : none ;
z-index : 0 ;
opacity : 0.44 ;
animation : drift 11 s ease-in-out infinite ;
}
. aurora . a1 {
width : 360 px ;
height : 360 px ;
left : -90 px ;
top : 40 px ;
background : rgba ( 175 , 134 , 255 , 0.9 );
}
. aurora . a2 {
width : 300 px ;
height : 300 px ;
right : -60 px ;
top : 180 px ;
background : rgba ( 81 , 218 , 255 , 0.86 );
animation-delay : -4 s ;
}
. aurora . a3 {
width : 340 px ;
height : 340 px ;
left : 40 % ;
bottom : -110 px ;
background : rgba ( 255 , 126 , 203 , 0.74 );
animation-delay : -7 s ;
}
@ keyframes drift {
0 %, 100 % { transform : translate3d ( 0 , 0 , 0 ) scale ( 1 ); }
50 % { transform : translate3d ( 0 , -22 px , 0 ) scale ( 1.05 ); }
}
. shell {
position : relative ;
z-index : 1 ;
width : min ( 1280 px , calc ( 100 % - 28 px ));
margin : 18 px auto 28 px ;
display : grid ;
gap : 16 px ;
}
. panel {
position : relative ;
border-radius : 28 px ;
border : 1 px solid var ( -- line );
background : var ( -- panel );
backdrop-filter : blur ( 16 px );
-webkit- backdrop-filter : blur ( 16 px );
box-shadow : var ( -- shadow );
padding : 24 px ;
overflow : hidden ;
isolation : isolate ;
}
. panel :: before {
content : "" ;
position : absolute ;
inset : -40 % 50 % auto -20 % ;
height : 80 % ;
transform : rotate ( -8 deg );
background : linear-gradient ( 120 deg , 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 : 18 px ;
padding-bottom : 26 px ;
background : linear-gradient ( 150 deg , var ( -- panel ), rgba ( 255 , 255 , 255 , 0.02 ));
}
. hero-toolbar {
display : flex ;
justify-content : center ;
align-items : center ;
gap : 12 px ;
margin-bottom : 10 px ;
}
. tag {
display : inline-flex ;
align-items : center ;
gap : 8 px ;
border : 1 px solid var ( -- line );
border-radius : 999 px ;
background : rgba ( 255 , 255 , 255 , 0.06 );
color : var ( -- sub );
padding : 7 px 14 px ;
font-weight : 600 ;
font-size : 0.85 rem ;
}
. theme-toggle {
border : none ;
border-radius : 999 px ;
background : var ( -- button - bg );
color : var ( -- btn - text );
padding : 8 px 16 px ;
font-size : 0.86 rem ;
font-weight : 800 ;
cursor : pointer ;
transition : transform 0.2 s ease , filter 0.2 s ease ;
box-shadow : 0 8 px 24 px rgba ( 87 , 175 , 255 , 0.24 );
}
. theme-toggle : hover {
transform : translateY ( -2 px );
filter : saturate ( 1.1 );
}
. hero-title {
margin : 6 px auto 8 px ;
max-width : 940 px ;
font-size : clamp ( 1.42 rem , 1.08 rem + 1.74 vw , 2.34 rem );
line-height : 1.2 ;
letter-spacing : 0.01 em ;
text-wrap : balance ;
background : linear-gradient ( 120 deg , #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 ( 120 deg , #0f1b44 12 % , #2f3f87 34 % , #7b53ff 60 % , #0086a8 84 % );
-webkit- background-clip : text ;
background-clip : text ;
}
. hero-sub {
margin : 0 auto ;
max-width : 760 px ;
color : var ( -- sub );
line-height : 1.7 ;
}
. authors , . meta-row , . links {
margin-top : 14 px ;
display : flex ;
justify-content : center ;
align-items : center ;
flex-wrap : wrap ;
gap : 10 px ;
}
. author-chip {
display : inline-flex ;
align-items : center ;
text-decoration : none ;
color : var ( -- text );
border : 1 px solid var ( -- line );
background : var ( -- chip );
border-radius : 999 px ;
padding : 8 px 12 px ;
font-size : 0.9 rem ;
transition : transform 0.2 s ease , border-color 0.2 s ease ;
}
. author-chip : hover {
transform : translateY ( -2 px );
border-color : rgba ( 157 , 188 , 255 , 0.48 );
}
. meta-row {
color : var ( -- sub );
font-size : 0.93 rem ;
gap : 8 px 14 px ;
}
. links { gap : 10 px 12 px ; }
. link-pill {
display : inline-flex ;
align-items : center ;
gap : 8 px ;
text-decoration : none ;
color : var ( -- text );
font-weight : 700 ;
font-size : 0.93 rem ;
border : 1 px solid var ( -- line );
border-radius : 12 px ;
padding : 9 px 13 px ;
background : linear-gradient ( 140 deg , rgba ( 255 , 255 , 255 , 0.14 ), rgba ( 255 , 255 , 255 , 0.02 ));
transition : transform 0.2 s ease , border-color 0.2 s ease , box-shadow 0.2 s ease ;
}
. link-pill : hover {
transform : translateY ( -2 px );
border-color : rgba ( 157 , 188 , 255 , 0.48 );
box-shadow : 0 10 px 22 px rgba ( 93 , 98 , 206 , 0.24 );
}
. section-head {
display : flex ;
justify-content : space-between ;
align-items : center ;
gap : 10 px ;
margin-bottom : 12 px ;
flex-wrap : wrap ;
}
. section-head h2 {
margin : 0 ;
font-size : clamp ( 1.06 rem , 0.93 rem + 0.96 vw , 1.6 rem );
letter-spacing : 0.01 em ;
}
. section-head p {
margin : 0 ;
color : var ( -- sub );
font-size : 0.93 rem ;
}
. demo-stack {
display : grid ;
gap : 14 px ;
}
. video-shell {
border-radius : 20 px ;
border : 1 px solid var ( -- line );
background : var ( -- panel - strong );
padding : 12 px ;
}
. video-chip {
display : inline-flex ;
margin-bottom : 10 px ;
border-radius : 999 px ;
border : 1 px solid rgba ( 164 , 151 , 255 , 0.34 );
background : rgba ( 167 , 139 , 250 , 0.12 );
color : var ( -- sub );
padding : 5 px 10 px ;
font-size : 0.79 rem ;
font-weight : 700 ;
letter-spacing : 0.03 em ;
text-transform : uppercase ;
}
. video-frame {
border-radius : 14 px ;
overflow : hidden ;
border : 1 px solid rgba ( 143 , 167 , 255 , 0.2 );
background : #02040d ;
aspect-ratio : 16 / 9 ;
min-height : 230 px ;
box-shadow : inset 0 0 0 1 px rgba ( 255 , 255 , 255 , 0.04 );
}
. video-frame video {
width : 100 % ;
height : 100 % ;
display : block ;
object-fit : contain ;
}
. overlay-player {
position : relative ;
border-radius : 14 px ;
overflow : hidden ;
border : 1 px solid rgba ( 143 , 167 , 255 , 0.2 );
background : #02040d ;
aspect-ratio : 16 / 9 ;
min-height : 230 px ;
box-shadow : inset 0 0 0 1 px rgba ( 255 , 255 , 255 , 0.04 );
}
. overlay-video {
position : absolute ;
inset : 0 ;
width : 100 % ;
height : 100 % ;
object-fit : contain ;
opacity : 0 ;
transition : opacity 0.8 s ease ;
}
. overlay-video . active { opacity : 1 ; }
. overlay-controls {
margin-top : 11 px ;
display : flex ;
justify-content : center ;
flex-wrap : wrap ;
gap : 8 px ;
}
. overlay-btn {
border-radius : 999 px ;
border : 1 px solid var ( -- line );
background : rgba ( 255 , 255 , 255 , 0.09 );
color : var ( -- text );
padding : 8 px 12 px ;
font-size : 0.86 rem ;
font-weight : 700 ;
cursor : pointer ;
transition : transform 0.2 s ease , border-color 0.2 s ease , background 0.2 s ease ;
}
. overlay-btn . active {
border-color : rgba ( 144 , 200 , 255 , 0.62 );
background : linear-gradient ( 135 deg , rgba ( 159 , 123 , 255 , 0.3 ), rgba ( 55 , 213 , 255 , 0.3 ));
}
. overlay-btn : hover { transform : translateY ( -1 px ); }
. status {
color : var ( -- sub );
font-size : 0.92 rem ;
margin-bottom : 10 px ;
}
. lottie-rows {
display : grid ;
gap : 10 px ;
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 : 16 px ;
border : 1 px solid var ( -- line );
background : var ( -- card - bg );
padding : 6 px 0 ;
}
. lane-track {
display : flex ;
gap : 10 px ;
align-items : stretch ;
will-change : transform ;
padding : 2 px 8 px ;
}
. lottie-card {
width : clamp ( 120 px , 12.2 vw , 178 px );
border-radius : 14 px ;
border : 1 px solid rgba ( 141 , 163 , 255 , 0.24 );
background : linear-gradient ( 140 deg , rgba ( 255 , 255 , 255 , 0.14 ), rgba ( 255 , 255 , 255 , 0.03 ));
flex : 0 0 auto ;
padding : 8 px ;
display : grid ;
gap : 6 px ;
transition : transform 0.2 s ease , border-color 0.2 s ease ;
}
. lottie-card : hover {
transform : translateY ( -2 px ) scale ( 1.01 );
border-color : rgba ( 157 , 188 , 255 , 0.6 );
}
. lottie-canvas {
height : clamp ( 70 px , 7.8 vw , 118 px );
border-radius : 10 px ;
overflow : hidden ;
background : rgba ( 8 , 11 , 22 , 0.65 );
border : 1 px 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.72 rem ;
white-space : nowrap ;
overflow : hidden ;
text-overflow : ellipsis ;
}
. tech-grid {
display : grid ;
gap : 12 px ;
grid-template-columns : 1 fr ;
}
. tech-card {
border-radius : 18 px ;
border : 1 px solid var ( -- line );
background : var ( -- card - bg );
padding : 14 px ;
}
. tech-card h3 {
margin : 0 ;
font-size : 1 rem ;
}
. tech-card p {
margin : 8 px 0 10 px ;
color : var ( -- sub );
line-height : 1.7 ;
font-size : 0.93 rem ;
}
. tech-card img {
width : 100 % ;
border-radius : 12 px ;
border : 1 px solid rgba ( 141 , 163 , 255 , 0.18 );
display : block ;
background : rgba ( 0 , 0 , 0 , 0.2 );
}
. citation-box {
border-radius : 16 px ;
border : 1 px solid var ( -- line );
background : rgba ( 4 , 9 , 24 , 0.76 );
padding : 12 px ;
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.87 rem ;
}
. citation-actions {
margin-top : 10 px ;
display : flex ;
justify-content : flex-end ;
}
. copy-btn {
border : 1 px solid var ( -- line );
border-radius : 999 px ;
background : rgba ( 255 , 255 , 255 , 0.1 );
color : var ( -- text );
font-weight : 700 ;
padding : 7 px 12 px ;
cursor : pointer ;
}
. footer {
text-align : center ;
color : var ( -- sub );
font-size : 0.86 rem ;
padding-bottom : 3 px ;
}
@ media ( max-width : 980px ) {
. panel { padding : 18 px ; border-radius : 22 px ; }
. video-frame , . overlay-player { min-height : 190 px ; }
. tech-grid { grid-template-columns : 1 fr ; }
}
@ media ( max-width : 640px ) {
. shell { width : calc ( 100 % - 16 px ); }
. hero-title { font-size : clamp ( 1.25 rem , 1.05 rem + 1.75 vw , 1.84 rem ); }
. video-frame , . overlay-player { min-height : 150 px ; }
. lottie-card { width : clamp ( 116 px , 35 vw , 150 px ); }
. lottie-canvas { height : clamp ( 66 px , 20 vw , 95 px ); }
}
</ style >
2026-02-12 21:31:55 +08:00
</ head >
< body >
2026-02-28 08:48:50 +00:00
< 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 >
2026-02-12 21:31:55 +08:00
</ section >
2026-02-28 08:48:50 +00:00
< 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" >
2026-02-28 12:16:19 +00:00
< video src = "./assets/OmniLottie-main-demo.mp4" autoplay muted loop playsinline ></ video >
2026-02-28 08:48:50 +00:00
</ 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 >
2026-02-12 21:31:55 +08:00
</ section >
2026-02-28 08:48:50 +00:00
< 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 >
2026-02-12 21:31:55 +08:00
</ section >
2026-02-28 08:48:50 +00:00
< 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 >
2026-02-12 21:31:55 +08:00
</ section >
2026-02-28 08:48:50 +00:00
< 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 >
2026-02-12 21:31:55 +08:00
</ section >
2026-02-28 12:10:48 +00:00
< div class = "footer" > OpenVGLab/OmniLottie</ div >
2026-02-28 08:48:50 +00:00
</ 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 ;
2026-02-28 09:10:42 +00:00
const switchTo = ( index , fromStart = false ) => {
2026-02-28 08:48:50 +00:00
videos . forEach (( video , i ) => {
const active = i === index ;
video . classList . toggle ( 'active' , active );
if ( active ) {
2026-02-28 09:10:42 +00:00
if ( fromStart ) video . currentTime = 0 ;
2026-02-28 08:48:50 +00:00
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 );
2026-02-28 09:10:42 +00:00
}, 10000 );
2026-02-28 08:48:50 +00:00
}
buttons . forEach (( button ) => {
button . addEventListener ( 'click' , () => {
const idx = Number ( button . dataset . index || 0 );
2026-02-28 09:10:42 +00:00
switchTo ( idx , true );
2026-02-28 08:48:50 +00:00
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 ,
2026-02-28 12:04:02 +00:00
speed : ( 0.34 + laneIndex * 0.08 ) * 1.5 ,
2026-02-28 08:48:50 +00:00
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 ;
2026-02-28 12:04:02 +00:00
if ( state . offset < 1 ) break ;
2026-02-28 08:48:50 +00:00
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 ) {
2026-02-28 09:10:42 +00:00
statusEl . textContent = 'No Lottie files found, please check the assets/demo_new_set directory or manifest.json' ;
2026-02-28 08:48:50 +00:00
return ;
}
2026-02-28 12:04:02 +00:00
const rows = [[], [], [], [], []];
files . forEach (( file , index ) => rows [ index % 5 ]. push ( file ));
2026-02-28 08:48:50 +00:00
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 );
2026-02-28 12:16:19 +00:00
btn . textContent = 'Copied Citation' ;
2026-02-28 09:10:42 +00:00
setTimeout (() => { btn . textContent = 'Copied Citation' ; }, 1200 );
2026-02-28 08:48:50 +00:00
} catch ( _ ) {
2026-02-28 12:16:19 +00:00
btn . textContent = 'Copy Citation Failed' ;
2026-02-28 09:10:42 +00:00
setTimeout (() => { btn . textContent = 'Copied Citation' ; }, 1200 );
2026-02-28 08:48:50 +00:00
}
});
}
window . addEventListener ( 'DOMContentLoaded' , () => {
initTheme ();
initOverlayPlayer ();
initLottieWall ();
initCitationCopy ();
});
</ script >
2026-02-12 21:31:55 +08:00
</ body >
</ html >