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. You can use the CSS from another Pen by using it's URL and the proper URL extention.

+ 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

              
                <div id='title'>
  <h1>GSAP Bezier Curve Viewer</h1>
  <h3>Bezier curves in GSAP are hard to visualize - this should make things easier.</h3>  
  <p>Notes 
    <ul>
      <li>"Thru" curves use the objects starting position as the first control point and go through successive points. They also have an additional "curviness" setting. </li>
      <li>"Soft" curves use the objects starting position as the first control point and have two control points per anchor point.</li>
      <li>"Quadratic" curves have one control point per anchor point (plus one starting point) </li>
      <li>"Cubic" curves have two control points per anchor point (plus one starting point)</li>
    </ul>
    </p>
</div>

<div id='container'>
  <div id='content'>
    <div id='build-curve'>
        Select the curve type <select id='type'>
        <option value="thru">Thru (default)</option>
        <option value="soft">Soft</option>
        <option value="quadratic">Quadratic</option>
        <option value="cubic">Cubic</option>
      </select><br>
      <button id="add-curve">Add a curve</button>
      <button id="rem-curve" style="display: none;">Remove a curve</button>
       
    </div>
    <div id='controls'>
      Speed <input type="text" size="3em" id="speed" value="5"/><br>
      Time Resolution <input type="text" size="3em" id="timeResolution" value="6"/><br>
      <div class="hide-thru">Curviness <input type="text"  size="3em" id="curviness" value="5" /><br></div>
      Properties
      <select id='properties'>
        <option value="leftTop">Left, Top</option>
        <option value="xy">X, Y</option>
      </select><br>
      Start Paused<input type="checkbox"  id="paused" checked/>
      Repeat<input type="checkbox"  id="repeat"/><br>
      Yoyo<input type="checkbox"  id="yoyo"/>
      AutoRotate<input type="checkbox"  id="autoRotate"/><br>
      <button id='playBtn'>Play</button>
    </div>
    

  </div>
</div>

<div id="show-curve"></div>
<div id="show-points"></div>
<div id='goose'></div>

<div id='code-container'>
  <h3>Generated Code (ES6)</h3> 
  <pre id='code'></pre>
</div>
  
              
            
!

CSS

              
                body{
	font-family: 'Roboto', sans-serif;
	margin: 0;
	top: 0;
	left: 0;
  	background-color: black;
}

h1{
	font-weight: 900;
  color: darkgrey;
}

h2,h3{
	font-weight: 700;
  color: darkgrey;
  text-align: center;
}

p{
  color: darkgrey;
  text-align: center;
}

li{
  text-align: left;
  color: darkgrey;
}

.point-text{
  font-weight: 200;
}

#title{
	position: absolute;
	top: 3%;
	left: 5%;
	width: 90%;
	height: auto;
	background-color: black;
	text-align: center;
}

#container{
	position: absolute;
	top: 20%;
	left: 3%;
	width: 94%;
	height: auto;
	min-height: 77%;
	background-color: black;
	border: solid #7F7F7F 1px;
	border-radius: 4px;
  background-color: Transparent;
}

#code-container{
  position: absolute;
	top: 100%;
	left: 3%;
	width: 94%;
	height: auto;
	min-height: 77%;
  margin-bottom: 3%;
	background-color: black;
	border: solid #7F7F7F 1px;
	border-radius: 4px;
  background-color: Transparent;
}

#content{
	position: relative;
	top: 0;
	left: 0;
	width: inherit;
	height: inherit;

}

#build-curve{
  background-color: Transparent;
	position: absolute;
  height: auto;
	left: 2%;
	top: 2%;
	width: 45%;
	color: #7F7F7F;
}

#controls{
	position: absolute;
	margin-top: 2%;
  margin-right: 0;
	right: 2%;
	width: 45%;
	color: #7F7F7F;
	text-align: right;
}

input, option, select, button{
    background-color: Transparent;
    background-repeat: no-repeat;
    cursor:pointer;
    overflow: hidden;
    outline:none;
    color: #7F7F7F;
    border: solid #7F7F7F 1px;
    border-radius: 2px;
}

#code{
  background-color: Transparent;
	position: absolute;
	left: 2%;
	top: 100px;
  height: auto;
	width: 90%;
	color: #7F7F7F;
}

button:hover {
	background-image: linear-gradient(to bottom, #252525, #000000);
}

#playBtn{
  width: 70%;
  height: 50%;
  line-height: 1.5;
  font-weight: 700;
}

#goose{
  position: absolute;
  left: 85%;
  top: 45%;
  height: 15%;
  width: 15%;
  background: url(https://dl.dropbox.com/s/4075h5fbiw4bjll/goose.png) 0px 0% / contain no-repeat;
}


              
            
!

JS

              
                ( () => { 
	"use strict";

	let config = {
		playing: false,
		paused: true,
		repeat: 0,
		yoyo: false,
		autoRotate: false,
		animSpeed: 5,
		type: 'thru',
		timeResolution: 6,
		ease: Linear.easeNone,
		properties: "leftTop",
		curviness: 5,
		curves: 1,
		//force3d: true,
	};

	let windowWidth = () =>  $(window).outerWidth(true);

	let windowHeight = () =>  $(window).outerHeight(true); 
  
  const WW = windowWidth();
  const WH = windowHeight();

	//Return a window height % as a pixel value
	let yPercentToPx = (y) => windowHeight() / 100 * y;

	//Return a window width % as a pixel value
	let xPercentToPx = (x) => windowHeight() / 100 * x;

	let setElementSizes = (goose) => {
		let $container = $('#container');    
		let spacing = 30;

		let titleHeight = $('#title').height();

		$container.css({top: (titleHeight + spacing) + "px" })

		$container.css({height: ($("#build-curve").height() + spacing) + "px" })

		$('#content').css({ height: $container.height() +"px" });

		$("#code").css({ bottom: "2%" });

		$("#code-container").css({top: titleHeight + spacing + $container.height() + spacing +"px"})
    
    goose.left = windowWidth() - parseFloat($("#controls").css("width"))/2;
    goose.top =  titleHeight + 150 + $("#controls").height();

    $("#goose").css({
      left: goose.left, 
      top: goose.top,
    })
	}
  
  	// ***********************************************************************************
  	// *
  	// *  This function allows us to visualize TweenMAx Bezier curves by drawing a 
  	// *  series of points along the curve
  	// *
  	// ***********************************************************************************
	let visualizeTweenMaxBezier = (tween, steps) => {
		//remove any existing curve
		$("#show-curve").empty();

		for (let i = 0; i < steps-1; i++){

			tween.progress(i/steps);

			$("#show-curve").append("<div id='point" + i + "'></div>");

			$("#point"+i).css({
				position: "absolute",
				width: "2px",
				height: "2px",
				"background-color": "#7F7F7F",
				top: tween.target.css("top"),
				left: tween.target.css("left"),
			});
		}
		tween.restart();
	}

	//Document ready
  	$(() => {
  		
		// ***********************************************************************************
		// *
		// *  Lets create a timeline to animate the goose
		// *  
		// *
		// ***********************************************************************************
		let $goose = $('goose');

		let goose = {
			elem: '#goose',
			left: 0,
			top: 0,
		}
    
    setElementSizes( goose );

		//Draggable.create(goose.elem, {
		//	onRelease: reset,
		//});

    let pointDist = WW/10;
    
      
		let values = [goose.left - pointDist];
      
    let d = 1; 
    for( let i = 1; i < 8 ; i++){
      if( i%2 === 0  ){
        values[i] = values[i-2] - pointDist; 
      }
      else{
        values[i] = goose.top + (pointDist * d)/2;
        d *= -1;
      }
    }
    
		let quadValues = values.slice(0,6);

		let createBezier = ( values ) => {
    		let curve = [];

    		//create an array of objects with either x,y or left, top attributes
    		for(let i=0; i < values.length; i += 2 ){
				if( config.properties === "leftTop"){
					curve.push({
						left: values[i],
						top: values[i+1],
					});
				}
				else{
					curve.push({
						x: values[i],
						y: values[i+1],
					});
				}
    		}
			return curve;
		}

		let bezier = createBezier( values );

		let buildTimeline = () => {
			let timeline = new TimelineMax({ 
				paused: config.paused,
				repeat: config.repeat, 
				yoyo: config.yoyo,
				onStart: () => {
					$('#playBtn').html('Pause'); 
					config.playing = true;
				},
				onComplete: () => {
					$('#playBtn').html('Reset'); 
					config.playing = false;
				}
			});

			let move = TweenMax.to(goose.elem, config.animSpeed, {
				bezier:{
					type: config.type,
					timeResolution : config.timeResolution,
					values:  bezier, 
					autoRotate: config.autoRotate,
				}, 
        ease: config.ease, 
			});

			timeline.add(move, 0);

			visualizeTweenMaxBezier(move, 200);

			return timeline;
		}

		goose.timeline = buildTimeline();

		// ***********************************************************************************
		// *
		// *  Lets do it all again but put it to the screen so that it can be copied
		// *  
		// *
		// ***********************************************************************************

		let displayCode = () => {
			let code = "";
			let timeline = "let timeline = new TimelineMax({ \n"
			timeline +="    paused: " + config.paused + ", \n"
			timeline +="    repeat: " +  config.repeat + ", \n"
			timeline +="    yoyo: " +  config.yoyo + ","
			timeline +=" \n});"
			
			code += timeline;

			let curve = "\n\nlet curve =";

			curve += "[\n";
			//create an array of objects with either x,y or left, top attributes
    		for(let i=0; i < values.length; i += 2 ){
    			if( config.properties === "leftTop"){
    				curve += "    {\n";
    				curve +="        left: " + values[i] +",\n";
    				curve +="        top: " + values[i+1] +",\n";
					curve += "    },\n"
    			}
    			else{
    				curve += "    {\n";
    				curve +="        x: " + values[i] +",\n";
    				curve +="        y: " + values[i+1] +",\n";
					curve += "    },\n"
    			}
    		}
    		curve += "]\n";

    		code += curve;

    		let tween = "\nlet tween = TweenMax.to(goose.elem, config.animSpeed, {\n";
			tween  +="    bezier:{\n";
			tween  +="        type: " + config.type + ",\n";
			tween  +="        curviness: " + config.curviness + ",\n";
			tween  +="        timeResolution: " + config.timeResolution + ",\n";
			tween  +="        values: curve,\n"; 
			tween  +="        autoRotate: " + config.autoRotate +",\n";
			tween  +="	},\n"
      tween  +="    ease: Linear.easeNone,\n";; 
			tween  +="});\n";
			code += tween;

			code += "\ntimeline.add(tween, 0);";

			//count how many lines of code we have so that the textarea can be resized
			let lines = code.split(/[\n\r]/g).length + config.curves;

			let lineHeight = "15";

			$("#code").css({ 
				'line-height': lineHeight + "px",
				height: (lines*lineHeight) + "px",
			});
			$('#code').html(code);

			$("#code-container").css({ 
				height: (lines*lineHeight + 100) + "px",
			});
		}

		displayCode();

		// ***********************************************************************************
		// *
		// *  Reset everything after options/points have changed
		// *  
		// *
		// ***********************************************************************************

		let reset = () => {
        setElementSizes( goose );
	  		TweenMax.killAll();

	  		if( config.type === "soft" || config.type === "quadratic" ){
	  			bezier = createBezier( quadValues );
	  		}
	  		else{
	  			bezier = createBezier( values );
	  		}

	  	    goose.timeline = buildTimeline();
	  	    displayCode();
	  	    showPoints();
          

	  	}

	  	// ***********************************************************************************
		// *
		// *  Draw the control points on the screen
		// *  
		// *
		// ***********************************************************************************

		let showPoints = () => {
			
			function onDrag(i) {
			    return function(e) {
			    	let $target = $('#' + this.target.attributes[0].nodeValue );
			    	
			    	if( config.type === "soft" || config.type === "quadratic" ){
			    		quadValues[i*2] = parseFloat( $target.css('left') );
						quadValues[(i*2)+1] = parseFloat( $target.css('top') );
			    	}
			    	else{
			    		values[i*2] = parseFloat( $target.css('left') );
						values[(i*2)+1] = parseFloat( $target.css('top') );
					}
				}
			};

			//get rid of any existing points
			$("#show-points").empty();

			let i = 0;
			for(let point of bezier){
				$("#show-points").append("<div id='control-point" + i + "'></div>");

				$("#control-point"+i).css({
					position: "absolute",
					width: "8px",
					height: "8px",
					"background-color": "#5422A7",
					"border-radius": "40px",
					border: "solid #7F7F7F 1px",
					top: point[Object.keys(point)[1]] + "px",
					left: (point[Object.keys(point)[0]] - 3 )+ "px",
				});

				Draggable.create("#control-point"+i, {
					type: "top,left",
					onDrag: onDrag(i),
					onRelease: reset,
				});

				i++;
			}
		}

		showPoints();

		// ***********************************************************************************
		// *
		// *  Set up the play/pause/reset button
		// *  
		// *
		// ***********************************************************************************
		$('#playBtn').click( ( ) => {
			if($('#playBtn').html() === 'Reset'){
				goose.timeline.restart();
				goose.timeline.play();
				$('#playBtn').html('Pause') 
			}
			if($('#playBtn').html() === 'Play'){
				goose.timeline.play();
				$('#playBtn').html('Pause') 
			}
			else{
				$('#playBtn').html('Play') 
				goose.timeline.pause();
			}
		});
	  	
	  	// ***********************************************************************************
	  	// *
	  	// *  Set up options for the timeline
	  	// *  
	  	// *
	  	// ***********************************************************************************

	  	//Whether animation should be paused at the start
	  	$('#paused').click( ( ) => {
	  	    config.paused = $("#paused").is(":checked") ? true : false;
	  	    reset();
	  	});
	  	//repeat infinitely or once
	  	$('#repeat').click( ( ) => {
	  	    config.repeat = $("#repeat").is(":checked") ? -1 : 0;
	  	    reset();
	  	});
	  	$('#yoyo').click( ( ) => {
	  	    config.yoyo = $("#yoyo").is(":checked") ? true : false;
	  	    reset();
	  	});
	  	//autoRotate or not, with 180 degrees added to stop the goose being upside down
	  	$('#autoRotate').click( ( ) => {
	  	    config.autoRotate = $("#autoRotate").is(":checked") ? 180 : false;
	  	    TweenMax.killAll();
	  	    TweenMax.to(goose.elem, 0, { rotationZ: 0, ease:Quad.easeInOut });
	  	    goose.timeline = buildTimeline();
	  	});

	  	//overall animation speed across all curves
	  	$("#speed").keyup(function(e){
	  		if ( this.value === "" ) { //don't run if no text has been entered
		        return false;
		    };
	  		if( isNaN(this.value) || this.value < 0 ){
	  			this.value = this.defaultValue;
			}
	  		config.animSpeed = this.value;
	  	    
	  	    reset();
	  	});

	  	//set the time resolution
	  	$("#timeResolution").keyup(function(e){
	  		if ( this.value === "" ) { //don't run if no text has been entered
		        return false;
		    };
	  		if( isNaN(this.value) || this.value < 0 ){
	  			this.value = this.defaultValue;
	  		}
	  		config.timeResolution = this.value;
	  	    
	  	    reset();
	  	});

	  	//thru, soft, quadratic, or cubic
	  	//hides the curviness option for all but soft
	  	//and second control points for quadratic
	  	$('#type').change( () => { 
	  		config.type = $('#type').val(); 
			reset();
	  	});

	  	//animate via left and top or x and y
	  	$('#properties').change( () => { 
	  		config.properties = $('#properties').val();
	  		reset();
	  	});

	  	//curviness value for thru type curves
	  	$("#curviness").keyup(function(e){
	  		if ( this.value === "" ) { //don't run if no text has been entered
		        return false;
		    };
	  		if( isNaN(this.value) || this.value < 0 ){
	  			this.value = this.defaultValue;
	  		}

	  		config.curviness = this.value;
	  	    reset();
	  	});

	  	//add a curve and resize elements if we have lots of curves
	  	$("#add-curve").click( () => {
	  		config.curves ++;
	  		let num = config.curves + 1;

	  		let len = values.length;

	  		for(let i = 0; i < 6; i++){
	  			if(i%2 === 0){
	  				values.push(values[ values.length - 2 ] - 50 );
	  			}
	  			else{
	  				values.push(values[ i ] );
	  			}
	  		}

	  		for(let i = 0; i < 4; i++){
	  			if(i%2 === 0){
	  				quadValues.push(values[ values.length - 2 ] - 50 );
	  			}
	  			else{
	  				quadValues.push(values[ i ] );
	  			}
	  		}

	  		$("#rem-curve").show();
	  		reset();
	  	});

	  	//remove the last added curve and resize elements if neccessary
	  	$("#rem-curve").click( () => {
	  		config.curves --;

	  		if(config.curves < 2 ){
	  			$("#rem-curve").hide();
	  		}

	  		for(let i = 0; i < 6; i++){
	  			values.pop();
	  		}

	  		for(let i = 0; i < 4; i++){
	  			quadValues.pop();
	  		}

  			reset();
	  	});

	  	//Detect changes to any of the points and update the curve
	  	$("#points :text").change( function(e){
	  		if ( this.value === "" ) { //don't run if no text has been entered
		        return false;
		    };
		    reset();
	  	})

	  	$(window).resize(_.debounce( () => {
	  	    setElementSizes( goose );
			reset();
	  	}, 300));

  	}); //end document ready

})();
              
            
!
999px

Console