Percentage-Based Keyframes

console.log(gsap.version); // need 3.9+
const slimes = gsap.utils.toArray(".star");
const tl = gsap.timeline();
  tl.paused( true )
    .to(".star", {
        keyframes:{
            "25%":{y:0},
            "50%":{y:-100, ease:"sine"},
            "75%":{y:0, ease:"sine.in"},
            "100%":{x:450, ease:"none"}
    },
    duration:2, 
    stagger:0.5
})

play.addEventListener("click", ()=> tl.restart());

Keyframes

Ex)

// timeline
let tl = gsap.timeline();
tl.to(".box", {
    x: 100
  })
  .to(".box", {
    y: 100
  })
  .to(".box", {
    x: 0
  })
  .to(".box", {
    y: 0
  });
// keyframes 
gsap.to(".box", {
  keyframes: {
    25%: {x:100, y:0}, 
    50%: {x:100, y:100}, 
    75%: {x:0, y:100, rotate: 360}, 
    100%: {y:0, x:0}, 
    easeEach: 'none'
  },
  duration: 4
});
// Array-based keyframes
gsap.to(".box", {
  keyframes: {
    x: [0, 100, 100, 0, 0],
    y: [0, 0, 100, 100, 0],
    ease: "power1.inOut"
  },
  duration: 2
});
// keyframes object
gsap.to(".box", {
  keyframes: [
    {x:100}, 
    {y:100, ease:'power2.out'}, 
    {x:0},
    {y:0, ease:'power2.in'}
  ],
  rotate:360, 
  transformOrigin:'center center'
});

Object keyframes – v3.0

gsap.to(".elem", {
 keyframes: [
  {x: 100, duration: 1, ease: 'sine.out'}, // finetune with individual eases
  {y: 200, duration: 1, delay: 0.5}, // create a 0.5 second gap
  {rotation: 360, duration: 2, delay: -0.25} // overlap by 0.25 seconds
 ],
 ease: 'expo.inOut' // ease the entire keyframe block
});

Percentage keyframes – v3.9

gsap.to(".elem", {
 keyframes: {
  "0%":   { x: 100, y: 100},
  "75%":  { x: 0, y: 0, ease: 'sine.out'}, // finetune with individual eases
  "100%": { x: 50, y: 50 },
   easeEach: 'expo.inOut' // ease between keyframes
 },
 ease: 'none' // ease the entire keyframe block
 duration: 2,
})

Simple Array-based keyframes – v3.9

gsap.to(".elem", {
 keyframes: {
  x: [100, 0, 50],
  y: [100, 0, 50]
  easeEach: 'sine.inOut' // ease between keyframes
  ease: 'expo.out' // ease the entire keyframe block
 },
 duration: 2,
})

Test 1

gsap.set("svg", { opacity: 1 });

gsap.to(".ball", {
  keyframes: {
    "0%": { yPercent: 0, scaleX: 1, scaleY: 1 },
    "7%": { yPercent: 5, scaleY: 0.9, scaleX: 1.1, ease: "sine.in" },
    "25%": { yPercent: 100, scaleY: 1.15, scaleX: 0.9, ease: "sine.in" },
    "50%": { yPercent: 500, scaleX: 1, scaleY: 1, ease: "none" },
    "60%": { scaleX: 1.6, scaleY: 0.4, ease: "none" },
    "65%": { yPercent: 500, scaleX: 1, scaleY: 1 },
    "100%": { yPercent: 0, scaleX: 1, scaleY: 1 },
    easeEach: "sine.out"
  },
  duration: 0.8,
  repeat: -1,
  transformOrigin: "center bottom"
});

gsap.to(".shadow", {
  scale: 0.7,
  duration: 0.4,
  repeat: -1,
  yoyo: true,
  ease: "sine.inOut",
  transformOrigin: "center"
});

Test 2

gsap.to(".box", {
  keyframes: {
    y: [0, 80, -10, 30, 0],
    ease: "none", // <- ease across the entire set of keyframes (defaults to the one defined in the tween, or "none" if one isn't defined there)
    easeEach: "power2.inOut" // <- ease between each keyframe (defaults to "power1.inOut")
  },
  rotate: 180,
  ease: "elastic", // <- the "normal" part of the tween. In this case, it affects "rotate" because it's outside the keyframes
  duration: 5,
  stagger: 0.2, 
  repeat: -1
});

Callbacks

gsap.to(".elem", {
 keyframes: [
  {x: 100, duration: 1},
  {y: 200, duration: 1, onComplete: () => { console.log('complete')}},
  {rotation: 360, duration: 2, delay: -0.25, ease: 'sine.out'}
 ]
});

gsap.to(".elem", {
 keyframes: {
  "0%":   { x: 100, y: 100},
  "75%":  { x: 0, y: 0, ease: 'power3.inOut'},
  "100%": { x: 50, y: 50, ease: 'none', onStart: () => { console.log('start')} }
 },
 duration: 2,
})