enjalot / blockbuilder

Create, fork and edit d3.js code snippets for use with bl.ocks.org right in the browser, no terminal required.
Other
323 stars 59 forks source link

eliminate the flickering when re-evaluating code in an iFrame. #188

Open micahstubbs opened 7 years ago

micahstubbs commented 7 years ago

screen shot 2017-02-05 at 4 55 52 pm

https://github.com/curran/example-viewer/pull/12

https://curran.github.io/d3-in-motion/#1/1/11

curran commented 7 years ago

It appears this could be implemented entirely within the updateIFrame() function in renderer.js:

https://github.com/enjalot/blockbuilder/blob/master/public/js/components/renderer.js#L89

curran commented 6 years ago

Here's the React implementation we're using in Datavis.tech:

import React, { Component } from 'react'
import magicSandbox from 'magic-sandbox'

// Z Indices for layering iframe buffers.
const BACK = 4
const FRONT = 5

const defaultUpdateInterval = 300

// This "dumb" component runs the code for a visualization in an iframe.
export default class RunnerRenderer extends Component {
  constructor (props) {
    super(props)
    const updateInterval = props.updateInterval || defaultUpdateInterval
    this.contentChanged = true
    this.swapped = true

    this.interval = setInterval(() => {
      if (this.iFrameRefA && this.iFrameRefB) {
        if (this.needsBufferSwap) {
          this.swapBuffers()
          this.needsBufferSwap = false
        }
        if (this.contentChanged) {
          this.updateBackBuffer()
          this.needsBufferSwap = true
          this.contentChanged = false
        }
      }
    }, updateInterval)
  }

  swapBuffers () {
    this.swapped = !this.swapped
    this.backBuffer().style.zIndex = BACK
    this.frontBuffer().style.zIndex = FRONT
  }

  updateBackBuffer () {
    const {template, files} = this.props
    const srcDoc = magicSandbox(template, files)
    this.backBuffer().setAttribute('srcDoc', srcDoc)
  }

  backBuffer () {
    return this.swapped ? this.iFrameRefA : this.iFrameRefB
  }

  frontBuffer () {
    return this.swapped ? this.iFrameRefB : this.iFrameRefA
  }

  componentWillUnmount () {
    clearInterval(this.interval)
  }

  shouldComponentUpdate (nextProps) {
    this.contentChanged = true
    return false
  }

  render () {
    const width = '100%'
    const height = '500'
    const style = { position: 'absolute', top: '0px', left: '0px' }

    return (
      <div style={{position: 'relative', height: height + 'px'}} >
        <iframe
          ref={el => { this.iFrameRefA = el }}
          style={style}
          width={width}
          height={height}
          scrolling='no'
        />
        <iframe
          ref={el => { this.iFrameRefB = el }}
          style={style}
          width={width}
          height={height}
          scrolling='no'
        />
      </div>
    )
  }
}
micahstubbs commented 6 years ago

thanks for sharing this @curran 😄

would be happy to review a PR to add this improvement if you'd like to contribute it in that form 😄

curran commented 6 years ago

I'm noticing Blockbuilder handles the rendering if the iFrame a bit differently, using a Blob source URL rather than setting srcDoc. It looks like a bit of refactoring will be required around https://github.com/enjalot/blockbuilder/blob/master/public/js/components/renderer.js#L89