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 is required to process package imports. If you need a different preprocessor remove all packages first.

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

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

              
                #app
              
            
!

CSS

              
                *
  box-sizing border-box

body
  margin 0
  padding 2rem
  background #222
  color #fff
  display flex
  align-items center
  justify-content center
  font-family -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif
  min-height 100vh

#app
  min-height 100vh
  display flex
  align-items center
  justify-content center
  flex-direction column
  width 50vmin
  min-width 200px
  transform-style preserve-3d
  perspective 1000px
  perspective-origin 50% 25%

.motion-element
  height 40px
  width 40px
  position absolute
  top 0%
  left 0%
  offset-path path(var(--path))
  animation travel 2s infinite var(--animation-direction, normal) linear
  transform-style var(--transform-style, 'none')
  transform translate3d(0, 0, 20px)

  &__side
    background rgba(128,191,255,0.1)
    border 2px #80bfff solid
    height 100%
    position absolute
    width 100%

    &:nth-of-type(1)
      transform translate3d(0, 0, 20px)
    &:nth-of-type(2)
      transform translate3d(0, 0, -20px)
    &:nth-of-type(3)
      transform rotateX(90deg) translate3d(0, 0, -20px)
    &:nth-of-type(4)
      transform rotateX(90deg) translate3d(0, 0, 20px)
    &:nth-of-type(5)
      transform rotateY(90deg) translate3d(0, 0, 20px)
    &:nth-of-type(6)
      transform rotateY(-90deg) translate3d(0, 0, 20px)

.container
  height 50vmin
  width 50vmin
  min-width 200px
  min-height 200px
  border 4px solid hsla(0, 0%, 100%, 0.5)
  overflow hidden
  position relative
  resize both
  margin-bottom 2rem
  transform-origin bottom center
  transform-style preserve-3d
  transform rotateX(calc(var(--rotation, 0) * 1deg))

button
  padding 8px 16px

details
  width 100%

summary
  margin-bottom 1rem
  padding 1rem 0

path
  fill none
  stroke 'hsl(%s, 100%, 50%)' % var(--hue, 260)
  stroke-width 4px
  transition stroke .25s ease

svg
  height 100%
  width 100%

@keyframes travel
  from
    offset-distance 0%
  to
    offset-distance 100%

label
  display block
  margin-bottom 0.5rem
  font-weight bold

input
  display block

[type=text]
[type=number]
  margin 0
  padding 8px 16px
  width 100%

a
  color 'hsl(%s, 100%, 50%)' % var(--hue)

p
  line-height 1.5
  text-align left
  width 100%

form
  display grid
  grid-gap 20px

.form-field
  margin-bottom 1.25rem

.form-field--grid
  display grid
  grid-template-columns auto 1fr
  grid-template-rows auto auto
  grid-gap 20px 10px
              
            
!

JS

              
                const {
  React: { Fragment, useEffect, useReducer, useRef },
  ReactDOM: { render },
} = window


const PATH =
  'M10.362 18.996s-6.046 21.453 1.47 25.329c10.158 5.238 18.033-21.308 29.039-18.23 13.125 3.672 18.325 36.55 18.325 36.55l12.031-47.544'

const INITIAL_STATE = {
  alternate: false,
  path: PATH,
  svg: true,
  height: 79.375,
  width: 79.375,
  threeD: false,
}

const formReducer = (state, action) => {
  switch (action.type) {
    case 'UPDATE':
      return { ...state, [action.name]: action.value }
    case 'DROP':
      return { ...state, ...action.data }
    default:
      return state
  }
}

const App = () => {
  const containerRef = useRef(null)
  const elementRef = useRef(null)
  const pathRef = useRef(null)
  const svgRef = useRef(null)
  const motionPathRef = useRef(null)
  const [state, dispatch] = useReducer(formReducer, INITIAL_STATE)
  const { alternate, path, svg, threeD, width, height } = state

  const onFileDrop = e => {
    e.preventDefault()
    const file = e.dataTransfer.files[0]
    if (
      file.type === 'image/svg+xml' ||
      file.name.slice(file.name.length - 4) === '.svg'
    ) {
      // process the file.
      const reader = new FileReader()
      reader.onloadend = response => {
        try {
          // file.target.result is the SVG markup we want to use.
          const wrapper = document.createElement('div')
          wrapper.innerHTML = response.target.result
          const svg = wrapper.querySelector('svg')
          const path = wrapper.querySelector('path')
          const viewBox = svg.getAttribute('viewBox').split(' ') // 0 0 x2 y2
          const pathString = path.getAttribute('d')
          dispatch({
            type: 'DROP',
            data: {
              path: pathString,
              width: viewBox[2],
              height: viewBox[3],
            },
          })
        } catch (e) {
          throw Error('Something went wrong', e)
        }
      }
      reader.readAsText(file)
    }
  }

  const prevent = e => e.preventDefault()

  const updateField = e =>
    dispatch({
      type: 'UPDATE',
      name: e.target.name,
      value: e.target.type === 'checkbox' ? e.target.checked : e.target.value,
    })

  useEffect(() => {
    if (containerRef.current) {
      const containerRefObserver = new ResizeObserver(entries => {
        if (motionPathRef.current) {
          const newPath = motionPathRef.current.generatePath(containerRef.current.offsetWidth, containerRef.current.offsetHeight)
          containerRef.current.style.setProperty('--path', `"${newPath}"`)
          pathRef.current.setAttribute('d', newPath)
        }
      })
      containerRefObserver.observe(containerRef.current)
    }
  }, [])

  useEffect(() => {
    if (containerRef.current && elementRef.current) {
      // Set up the initial responsive motion path
      motionPathRef.current = new Meanderer({
        path,
        height,
        width,
      })
      const newPath = motionPathRef.current.generatePath(containerRef.current.offsetWidth, containerRef.current.offsetHeight)
      containerRef.current.style.setProperty('--path', `"${newPath}"`)
      pathRef.current.setAttribute('d', newPath)
    }
  }, [path, width, height])

  useEffect(() => {
    document.body.addEventListener('dragover', prevent)
    document.body.addEventListener('drop', onFileDrop)
    return () => {
      document.body.removeEventListener('dragover', prevent)
      document.body.removeEventListener('drop', onFileDrop)
    }
  }, [])

  const hue = Math.random() * 360

  return (
    <Fragment>
      <div
        ref={containerRef}
        className="container"
        style={{
          '--rotation': threeD ? 75 : 0,
          overflow: threeD ? 'visible' : 'hidden',
        }}>
        <svg
          {...(!svg && { hidden: true })}
          ref={svgRef}
          style={{
            '--hue': hue,
          }}>
          <path ref={pathRef}></path>
        </svg>
        <div
          ref={elementRef}
          style={{
            '--animation-direction': alternate ? 'alternate' : 'normal',
            '--transform-style': threeD ? 'preserve-3d' : 'none',
          }}
          className="motion-element">
          <div className="motion-element__side"></div>
          <div className="motion-element__side"></div>
          <div className="motion-element__side"></div>
          <div className="motion-element__side"></div>
          <div className="motion-element__side"></div>
          <div className="motion-element__side"></div>
        </div>
      </div>
      <p
        style={{
          '--hue': hue,
        }}>
        Drag and drop an optimized SVG file onto the page that contains a path.
        Clean up your SVG first with{' '}
        <a
          href="https://jakearchibald.github.io/svgomg/"
          target="_blank"
          rel="noreferrer noopener">
          SVGOMG
        </a>
        . Alternatively, manually enter path info into the configuration form
        below.
      </p>
      <p>
        Resize the viewport and see your motion path scale!{' '}
        <span aria-label="TADA!" role="img">
          🎉
        </span>
      </p>
      <details>
        <summary>Path configuration</summary>
        <form onDrop={onFileDrop}>
          <section className="form-field">
            <label htmlFor="path">Path</label>
            <input
              id="path"
              type="text"
              name="path"
              value={path}
              onChange={updateField}
            />
          </section>
          <section className="form-field">
            <label htmlFor="width">Initial Width (viewBox x2)</label>
            <input
              id="with"
              type="number"
              name="width"
              value={width}
              onChange={updateField}
            />
          </section>
          <section className="form-field">
            <label htmlFor="height">Initial Height (viewBox y2)</label>
            <input
              id="height"
              type="number"
              name="height"
              value={height}
              onChange={updateField}
            />
          </section>
          <section className="form-field form-field--grid">
            <label htmlFor="svg">Show SVG path?</label>
            <input
              id="svg"
              type="checkbox"
              name="svg"
              checked={svg}
              onChange={updateField}
            />
            <label htmlFor="alternate">Alternate direction?</label>
            <input
              id="alternate"
              type="checkbox"
              name="alternate"
              checked={alternate}
              onChange={updateField}
            />
            <label htmlFor="threeD">See path in 3D?</label>
            <input
              id="threeD"
              type="checkbox"
              name="threeD"
              checked={threeD}
              onChange={updateField}
            />
          </section>
          <section className="form-field form-field--grid">
            <button
              onClick={e => {
                e.preventDefault()
                containerRef.current.removeAttribute('style')
              }}>
              Reset container size
            </button>
          </section>
        </form>
      </details>
    </Fragment>
  )
}

render(<App />, document.getElementById('app'))

              
            
!
999px

Console