div.marionette__container
  svg.marionette(xmlns='http://www.w3.org/2000/svg' viewBox='0 0 306.4384 100.049')
    g(transform='translate(168.1994 -94.4993)')
      g.marionette__body(transform='matrix(-1 0 0 1 186.7716 -32.291)')
        rect(width='7.9375' height='3.2128' x='109.7359' y='206.8494' ry='1.6064')
        rect(ry='1.6064' y='-69.7607' x='224.027' height='3.2128' width='7.9375' transform='rotate(78.712)')
        rect(width='7.9375' height='3.2128' x='28.5502' y='231.9518' ry='1.6064' transform='rotate(-20.815)')
        rect(ry='1.6064' y='146.0649' x='183.4736' height='3.2128' width='7.9375' transform='rotate(23.396)')
      g.marionette__body(transform='translate(-.7692 -30.7543)')
        rect(ry='1.6064' y='206.8494' x='109.7359' height='3.2128' width='7.9375')
        rect(transform='rotate(78.712)' width='7.9375' height='3.2128' x='224.027' y='-69.7607' ry='1.6064')
        rect(transform='rotate(-20.815)' ry='1.6064' y='231.9518' x='28.5502' height='3.2128' width='7.9375')
        rect(transform='rotate(23.396)' width='7.9375' height='3.2128' x='183.4736' y='146.0649' ry='1.6064')
      path.marionette__body(d='M104.0763 158.8212l-4.71 3.0476 7.4174 16.4625 4.71-3.0475zM80.3662 158.058l4.71 3.0475-7.4174 16.4626-4.71-3.0476z')
      path.marionette__shirt-two(d='M81.7845 151.0708l9.2606 5.8584-8.2874 13.1003-9.2606-5.8583z')
      path.marionette__short-one(d='M89.038 169.2584l-11.8608 3.8613 2.8665 8.8051s6.7645-4.0506 10.312-3.3564c2.5056.4904 5.8524 4.9413 5.8524 4.9413l5.813-5.1785-8.2677-6.9804-3.1274 2.7864z')
      ellipse.marionette__short-two(transform='matrix(.95521 -.29592 .3112 .95035 0 0)' ry='4.6121' rx='1.5358' cy='192.7823' cx='19.5399')
      path.marionette__body(d='M79.5594 175.535l-2.7357.9902-4.3403 1.572 1.3513 3.7315 4.3992-1.5932 2.4257-.8785a4.6122 1.5356 71.7541 00-.0263-.2268 4.6122 1.5356 71.7541 00-.0651-.414 4.6122 1.5356 71.7541 00-.0848-.4273 4.6122 1.5356 71.7541 00-.1028-.4356 4.6122 1.5356 71.7541 00-.1199-.4403 4.6122 1.5356 71.7541 00-.136-.4398 4.6122 1.5356 71.7541 00-.1048-.309 4.6122 1.5356 71.7541 00-.1602-.4295 4.6122 1.5356 71.7541 00-.172-.4175 4.6122 1.5356 71.7541 00-.1282-.2822z')
      path.marionette__shoe(d='M58.101 179.9147c1.5445 5.9953 3.974 10.6174 8.9232 9.7408 4.9493-.8766 11.0168-5.3135 9.4723-11.3089-1.5445-5.9953-8.8876-10.9953-13.8369-10.1187-4.9492.8766-6.103 5.6915-4.5585 11.6868z')
      ellipse.marionette__sole(cx='-25.5503' cy='188.628' rx='7.5983' ry='11.1004' transform='scale(-1 1) rotate(12.0575)')
      path.marionette__shirt-one(d='M85.259 152.2735l-3.4743 19.159c.9365.1459 1.5516 3.1172 3.0117 3.826 2.621 1.2726 6.2625 1.5319 8.741 0 .0711-.0439.0744-.1507.1122-.2242 0 0 .1307-.2424.1974-.3571a8.2155 8.2155 0 01.202-.3302c.068-.1051.1362-.205.2047-.2992a5.3143 5.3143 0 01.2051-.2651 4.0588 4.0588 0 01.2036-.229 2.9838 2.9838 0 01.2-.1906 2.0826 2.0826 0 01.1943-.1494 1.416 1.416 0 01.1871-.1075.9566.9566 0 01.1772-.0646.6682.6682 0 01.167-.0206.5307.5307 0 01.2537.0666.5974.5974 0 01.1282.0982.815.815 0 01.1116.14 1.202 1.202 0 01.094.1814c.0282.067.0532.1405.075.2202.0218.0799.0405.166.0558.2578a3.57 3.57 0 01.0361.2915c.0086.1028.0138.2107.0155.3235.0016.1126 0 .23-.0051.3514a7.6091 7.6091 0 01-.0207.307l3.7221-.378-3.5298-23.4871c-4.8026 6.0587-5.2385.943-11.2654.88z')
      g.marionette__logo(fill='none' stroke-width='2.1167' stroke-linecap='round' stroke-linejoin='round')
        path(d='M88.9034 166.5829l3.0926-1.7855v-3.5709l-3.0926-1.7854-3.0925 1.7854v3.571z' stroke-width='.755852403')
        path(d='M85.811 164.7974l3.0924-1.7854 3.0926 1.7854-3.0926 1.7855v-3.571l3.0926-1.7854-3.0926-1.7854-3.0925 1.7854 3.0925 1.7855M88.9034 159.441v3.571' stroke-width='.755852403')
      path.marionette__shirt-two(d='M103.3032 151.449l-9.7946 4.9137 6.9512 13.8558 9.7946-4.9138z')
      g(transform='rotate(12.0572 76.9171 -61.6913) scale(1.1222)')
        ellipse.marionette__shoe(ry='11.1004' rx='7.5983' cy='152.3086' cx='134.8652')
        ellipse.marionette__sole(cx='136.0782' cy='152.8694' rx='7.5983' ry='11.1004')
    g(transform='translate(168.1994 -94.4993)')
      ellipse.marionette__body(cx='122.5346' cy='105.4087' rx='23.4087' ry='27.8344' transform='rotate(12.7347) skewX(.1607)')
      g.marionette__eye
        g(transform='translate(171.6012 -106.5893)')
          circle(r='5.2917' cy='228.9643' cx='-83.5327')
          circle.marionette__pupil(r='1.8899' cy='227.8304' cx='-85.4226')
      g.marionette__eye
        g(transform='translate(187.0982 -102.8095)')
          circle(cx='-83.5327' cy='228.9643' r='5.2917')
          circle.marionette__pupil(cx='-85.4226' cy='227.8304' r='1.8899')
      path.marionette__cheek(d='M76.7292 127.6666a6.8036 6.8036 0 00-3.3874.9131 23.4586 29.6733 8.8137 00-.3421 4.5253 23.4084 27.8347 12.1858 001.2795 7.7045 6.8036 6.8036 0 002.45.464 6.8036 6.8036 0 006.8037-6.8032 6.8036 6.8036 0 00-6.8037-6.8037z')
      circle.marionette__cheek(r='6.8036' cy='140.5178' cx='105.0774')
      ellipse.marionette__mouth(cx='-16.5862' cy='170.7461' rx='3.2128' ry='4.5357' transform='rotate(-37.101)')
      ellipse.marionette__body(cx='139.6026' cy='110.7116' rx='3.7084' ry='5.6795' transform='rotate(9.433)')
      path.marionette__hair(d='M98.6632 102.2434a23.4084 27.8347 12.1858 00-16.0114 6.5587l36.11 9.8563a23.4084 27.8347 12.1858 00-16.1293-15.9334 23.4084 27.8347 12.1858 00-3.9693-.4816z')
    g(transform='translate(168.1994 -94.4993)')
      path.cap__accent(d='M120.1756 113.836c.0863.3307.2041.6883.2687 1.0087.1522.7547.2419 1.4962.2574 2.295.0155.7987-.0431 1.655-.187 2.6406-.144.9855-.3734 2.0998-.6998 3.4148 5.498.265 16.5349 2.5925 18.3896-.7912.6125-2.2416-6.9815-5.8505-18.0289-8.568zM84.9649 110.4343l17.6302 4.9355-.7385 2.638-17.6302-4.9354z')
      path.cap(d='M100.6853 94.665c-4.0217.075-8.5087.1886-11.8075 2.4903-4.4836 3.1283-7.996 7.6063-9.2444 14.952l6.6952 1.8475c.3415-.9966 1.2214-3.2373 2.4065-3.7367 3.1919-1.345 7.9428-.3392 10.1209 2.3538.8818 1.0904.4782 3.7134.2356 4.9046l20.7233 5.7186c1.3055-5.2599 2.8492-10.8349 1.5839-15.489-.615-2.262-2.3562-4.111-3.993-5.7888-1.718-1.761-3.7853-3.2074-5.9692-4.3398-1.9722-1.0227-4.1568-1.5995-6.3097-2.1477-1.456-.3707-2.9395-.7928-4.4416-.7648z')
      ellipse.cap__accent(cx='120.4369' cy='73.2447' rx='1.4366' ry='.6682' transform='rotate(11.183)')
  .marionette__nose
.story
  .story__content.story__content--top
    h1.blurb.blurb--one(data-splitting='') Once upon a time,
    h1.blurb.blurb--three(data-splitting='') And they lied
  .story__content.story__content--bottom
    h1.blurb.blurb--two(data-splitting='') There was a marionette.
    h1.blurb.blurb--four(data-splitting='') and lied, and lied, and lied, and lied.
View Compiled
*
  box-sizing border-box

:root
  --size 90
  --ar calc(194.8 / 596.69)
  --coeff 1vmin
  --width calc(var(--size) * var(--coeff))
  --height calc((var(--size) * var(--ar)) * var(--coeff))
  --bg hsl(248, 28%, 55%)
  --color hsl(0, 0%, 100%)
  --body hsl(33, 74%, 74%)
  --nose hsl(12, 71%, 74%)
  --cap hsl(0, 0%, 20%)
  --cap-accent hsl(58, 91%, 51%)
  --hair hsl(32, 25%, 45%)
  --shirt-one hsl(0, 0%, 29%)
  --shirt-two hsl(0, 0%, 17%)
  --short-one hsl(4, 80%, 39%)
  --short-two hsl(4, 80%, 32%)
  --shoe hsl(0, 0%, 5%)
  --sole hsl(22, 21%, 52%)
  --logo white
  --cheek hsla(4, 57%, 74%, 0.5)
  --pupil hsl(0, 0%, 95%)
  --mouth hsl(0, 0%, 10%)

body
  width 100vw
  font-family sans-serif
  overflow-x hidden
  background var(--bg)

.cap
  fill var(--cap)

  &__accent
    fill var(--cap-accent)

.marionette
  height 100%
  width 100%

  &__shoe
    fill var(--shoe)
  &__sole
    fill var(--sole)
  &__shirt-one
    fill var(--shirt-one)
  &__shirt-two
    fill var(--shirt-two)
  &__short-one
    fill var(--short-one)
  &__short-two
    fill var(--short-two)
  &__cheek
    fill var(--cheek)
  &__mouth
  &__eye circle:nth-of-type(1)
    fill var(--mouth)
  &__pupil
    fill var(--pupil)
  &__hair
    fill var(--hair)
  &__logo
    stroke var(--logo)

  &__body
    fill var(--body)

  &__container
    width var(--width)
    position fixed
    height var(--height)
    left 50%
    top 50%
    transform translate(-50%, -50%) scale(0)
    transform-origin 90% 50%

  &__nose
    position absolute
    top 36%
    right 15.5%
    width 0
    height 2.5vmin
    background var(--nose)

    &:after
    &:before
      content ''
      position absolute
      height 2.5vmin
      width 2.5vmin
      border-radius 50%
      background var(--nose)
    &:after
      left 0
      transform translate(-50%, 0)
    &:before
      transform translate(50%, 0)
      right 0


h1
  font-size 2rem
  font-weight 800
  color var(--color)

.story
  height 100vh
  width 100vw
  position fixed
  overflow hidden

  &__content
    position absolute
    width var(--width)
    left 50%
    transform translate(-50%, var(--y))

    &--bottom
      top 50%
      --y calc(0% + (var(--height) / 2))
    &--top
      bottom 50%
      --y calc(0% - (var(--height) / 2))

.story__content--bottom .blurb
  top 0
.story__content--top .blurb
  bottom 0

.blurb
  position absolute
  visibility hidden
  &--two
  &--three
  &--four
    .word
      opacity 0

View Compiled
const {
  gsap,
  ScrollTrigger,
  gsap: { timeline, set, to, from },
  Splitting,
} = window

Splitting()
set('.blurb', { visibility: 'visible' })
gsap.registerPlugin(ScrollTrigger)
window.scrollTo(0, 0)

const NOSE = document.querySelector('.marionette__nose')
const CONTAINER = document.querySelector('.marionette__container')
const MOUTH = document.querySelector('.marionette__mouth')
set(CONTAINER, { transformOrigin: '90% 50%', scale: 0 })
set(MOUTH, { transformOrigin: '50% 50%', scale: 0.2 })
set('.marionette__pupil', { transformOrigin: '50% 50%', scale: 0.75 })

const INC = 100
const PADDING = 200

const BLURB_ONE = [...document.querySelectorAll('.blurb--one .word')]
const BLURB_TWO = [...document.querySelectorAll('.blurb--two .word')]
const BLURB_THREE = [...document.querySelectorAll('.blurb--three .word')]
const BLURB_FOUR = [...document.querySelectorAll('.blurb--four .word')]

const BUFF_ONE = PADDING
// Once upon a time
BLURB_ONE.forEach((word, index) => {
  to(word, {
    scrollTrigger: {
      scrub: true,
      start: () => BUFF_ONE + index * INC,
      end: () => BUFF_ONE + index * INC + INC,
    },
    opacity: 0,
  })
})
// There was a marionette
const BUFF_TWO = BLURB_ONE.length * INC + INC + PADDING
BLURB_TWO.forEach((word, index) => {
  to(word, {
    scrollTrigger: {
      scrub: true,
      start: () => BUFF_TWO + index * INC,
      end: () => BUFF_TWO + index * INC + INC,
    },
    opacity: 1,
  })
})
// Show the marionette
to(CONTAINER, {
  scale: 1,
  scrollTrigger: {
    scrub: true,
    start: () => BUFF_TWO,
    end: () => BUFF_TWO + BLURB_TWO.length * INC + INC,
  },
})

// Hide the marionette text
const BUFF_THREE = BUFF_TWO + BLURB_TWO.length * INC + INC + PADDING
BLURB_TWO.forEach((word, index) => {
  to(word, {
    scrollTrigger: {
      scrub: true,
      start: () => BUFF_THREE + index * INC,
      end: () => BUFF_THREE + index * INC + INC,
    },
    opacity: 0,
  })
})

// And they lied
const BUFF_FOUR = BUFF_THREE + BLURB_TWO.length * INC + INC
BLURB_THREE.forEach((word, index) => {
  to(word, {
    scrollTrigger: {
      scrub: true,
      start: () => BUFF_FOUR + index * INC,
      end: () => BUFF_FOUR + index * INC + INC,
    },
    opacity: 1,
  })
})
const BUFF_FIVE = BUFF_FOUR + BLURB_THREE.length * INC + INC + PADDING
to(NOSE, {
  width: '75vmin',
  scrollTrigger: {
    scrub: true,
    start: () => BUFF_FOUR,
    end: () => BUFF_FIVE + BLURB_FOUR.length * INC + INC,
  },
})
to(MOUTH, {
  scale: 1,
  scrollTrigger: {
    scrub: true,
    start: () => BUFF_FOUR,
    end: () => BUFF_FIVE + BLURB_FOUR.length * INC + INC,
  },
})

BLURB_FOUR.forEach((word, index) => {
  to(word, {
    opacity: 1,
    scrollTrigger: {
      scrub: true,
      start: () => BUFF_FIVE + index * INC,
      end: () => BUFF_FIVE + index * INC + INC,
    },
  })
})

const blink = EYES => {
  gsap.set(EYES, { scaleY: 1 })
  if (EYES.BLINK_TL) EYES.BLINK_TL.kill()
  EYES.BLINK_TL = new gsap.timeline({
    delay: Math.floor(Math.random() * 4) + 1,
    onComplete: () => blink(EYES),
  })
  EYES.BLINK_TL.to(EYES, {
    duration: 0.05,
    transformOrigin: '50% 50%',
    scaleY: 0,
    yoyo: true,
    repeat: 1,
  })
}
const EYES = document.querySelectorAll('.marionette__eye')
blink(EYES)

document.body.style.height = `${BUFF_FIVE +
  BLURB_FOUR.length * INC +
  INC +
  PADDING +
  window.innerHeight}px`
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/ScrollTrigger.min.js
  2. https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/gsap-latest-beta.min.js
  3. https://unpkg.com/splitting/dist/splitting.min.js