Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                .block-reveal
  .block-reveal__line
    .block-reveal__word Looping Block
    .block-reveal__word Use it
    .block-reveal__word made with
    .block-reveal__block
  .block-reveal__line
    .block-reveal__word Reveal
    .block-reveal__word on your
    .block-reveal__word GreenSock
    .block-reveal__block
  .block-reveal__line
    .block-reveal__word Effect
    .block-reveal__word sites!
    .block-reveal__word 💪
    .block-reveal__block
              
            
!

CSS

              
                *
  box-sizing border-box

body
  background-color #fff
  text-align center
  font-family 'HelveticaNeue-Light', 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif
  min-height 100vh
  display flex
  align-items center
  justify-content center

button
  position fixed
  top 1rem
  left 1rem
  background 0
  opacity 0.5
  border 0
  cursor pointer
  transition opacity .15s ease
  font-weight bold
  letter-spacing 2px

  &:hover
    opacity 1

.block-reveal
  display inline-flex
  flex-direction column
  position relative

  &__line
    display grid
    // grid-template-columns 1fr
    justify-items flex-start
    position relative

  &__block
    background rebeccapurple
    bottom 0
    left 0
    position absolute
    right 0
    top 0
    transform-origin left
    transform scaleX(0)
    display inline-block

  &__word
    font-size 2.75rem
    display inline-block
    font-weight bold
    grid-column 1
    grid-row 1
    opacity 0
    padding 0 5px

.block-reveal__line:nth-of-type(1) .block-reveal__block
  background #2eec71
.block-reveal__line:nth-of-type(2) .block-reveal__block
  background #e74c3c
.block-reveal__line:nth-of-type(3) .block-reveal__block
  background #663399
              
            
!

JS

              
                const { TimelineMax, TweenMax } = window

const CLASSES = {
  BLOCK: 'block-reveal__block',
  LINE: 'block-reveal__line',
  WORD: 'block-reveal__word',
}
class BlockReveal {
  blockWidths = []
  element = undefined
  elementCache = {
    blocks: undefined,
    words: [],
  }
  index = 0
  ran = 0
  options = {
    delay: 2,
    repeat: 2,
    repeatDelay: 1,
    blockStagger: 0.2,
    blockSlide: 0.4,
  }
  wordsPerLine = undefined
  constructor(element, options) {
    // Store element on class
    this.element = element
    // Store options on class
    this.options = { ...this.options, ...options }
    // Set the wordsPerLine
    this.wordsPerLine = this.element
      .querySelector(`.${CLASSES.LINE}`)
      .querySelectorAll(`.${CLASSES.WORD}`).length
    const sameLineLength = [
      ...this.element.querySelectorAll(`.${CLASSES.LINE}`),
    ].every(
      v => v.querySelectorAll(`.${CLASSES.WORD}`).length === this.wordsPerLine
    )
    if (!sameLineLength)
      throw new Error('BlockReveal: Lines need to be same amount of words')
    // Cache the elements in advance so we aren't making querySelector calls all the time
    this.cacheElements()
    // Cache the block widths for each index
    this.cacheBlockWidths()
  }
  cacheElements = () => {
    // Cache the blocks
    this.elementCache.blocks = this.element.querySelectorAll(
      `.${CLASSES.BLOCK}`
    )
    // Cache the words in blocks of phrases
    for (let i = 0; i < this.wordsPerLine; i++) {
      this.elementCache.words.push(
        this.element.querySelectorAll(
          `.${CLASSES.LINE} .${CLASSES.WORD}:nth-of-type(${i + 1})`
        )
      )
    }
  }
  cacheBlockWidths = () => {
    // Iterate through the elementCache words
    this.elementCache.words.forEach(phrase => {
      const blockWidths = []
      // For each word in the phrase, calculate the correct block width
      phrase.forEach(word => {
        const current =
          word.previousElementSibling ||
          word.parentElement.children[this.wordsPerLine - 1]
        const wordWidth = Math.ceil(word.getBoundingClientRect().width)
        const currentWidth = Math.ceil(current.getBoundingClientRect().width)
        blockWidths.push(Math.max(wordWidth, currentWidth))
      })
      this.blockWidths.push(blockWidths)
    })
    /**
     * Push one last set of widths which equate to when everything is blank
     * and we are revealing the first set of words.
     */
    this.blockWidths.push(
      [...this.elementCache.words[this.index]].map(word =>
        Math.ceil(word.getBoundingClientRect().width)
      )
    )
  }
  start = () => {
    const {
      options: { delay, repeat, repeatDelay },
    } = this

    const TL = new TimelineMax({
      delay,
      repeat,
      repeatDelay,
    })
    const onStart = () => {
      const nextWords = this.elementCache.words[this.index]
      nextWords.forEach((word, index) => {
        const block = this.elementCache.blocks[index]
        block.style.width = `${
          this.blockWidths[this.ran ? this.index : this.wordsPerLine][index]
        }px`
      })
    }
    const onCompleteIn = () => {
      const {
        elementCache: { blocks, words },
        index,
        wordsPerLine,
      } = this
      const previousWords = words[index === 0 ? wordsPerLine - 1 : index - 1]
      const newWords = words[index]
      for (const word of previousWords) {
        word.style.opacity = '0'
      }
      for (const block of blocks) {
        block.style.transformOrigin = 'right'
      }
      for (const word of newWords) {
        word.style.opacity = '1'
      }
    }
    const onCompleteOut = () => {
      this.index = this.index + 1 > this.wordsPerLine - 1 ? 0 : this.index + 1
      this.ran += 1
    }
    TL.add(
      TweenMax.staggerTo(
        this.elementCache.blocks,
        this.options.blockSlide,
        { onStart, transformOrigin: 'left', scaleX: 1 },
        this.options.blockStagger,
        onCompleteIn
      )
    )
    TL.add(
      TweenMax.staggerTo(
        this.elementCache.blocks,
        this.options.blockSlide,
        { scaleX: 0 },
        this.options.blockStagger,
        onCompleteOut
      )
    )
  }
}

const block = document.querySelector('.block-reveal')
const myBlockReveal = new BlockReveal(block, {
  delay: 1,
  repeat: -1,
  repeatDelay: 1,
  blockStagger: 0.15,
  blockSlide: 0.5,
})
myBlockReveal.start()

              
            
!
999px

Console