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

              
                <!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>ZoneTracker: Drag and Drop Demo</title>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
        <script>
  console.log = (...args) => {
		const next = args.join('\n') + `\n`;
		document.getElementById("console").innerHTML = next + document.getElementById("console").innerHTML;
	}
	console.clear = () => {
		document.getElementById("console").innerHTML = "";
	}
        </script>
    </head>
    <body>
        <div id="container"></div>
        <div id="output">
            <pre id="console">Output will appear here...
Drag and drop the shapes to the right.</pre>
        </div>
      <img id="clock" src="https://cdn.kastatic.org/ka-perseus-graphie/a3880dac4fb72b9858f1cbb02b47d50516480678.svg">

        <script>
$(() => {
	function spinFor(prob, delay) {
		if (Math.random() > prob) {
		    delay = delay/2 + Math.random()*delay/2;
			} else {
			  delay = Math.random()*16;
			}

		const start = new Date();
		while (new Date().getTime() - start.getTime() < delay);
	}

	var width = window.innerWidth;
	var height = window.innerHeight;

	var stage = new Konva.Stage({
		container: 'container',
		width: width,
		height: height,
	});

	var layer = new Konva.Layer();
	var rectX = stage.getWidth() / 2 - 50;
	var rectY = stage.getHeight() / 2 - 25;

  const boxes = ['#00D2FF', '#FFD2FF', '#FFAA00'].map((c, i) => {
    return new Konva.Rect({
      x: 285 + i*150,
      y: rectY,
      width: 100,
      height: 50,
      fill: c,
      stroke: 'black',
      strokeWidth: 4,
      draggable: true
    });
  })


	boxes.forEach((b, i) => {
	  b.on('mouseover', function() {
		  document.body.style.cursor = 'pointer';
		  spinFor(.5, 100)
	  });
	  b.on('mouseout', function() {
		  document.body.style.cursor = 'default';
		  spinFor(.5, 100)
	  });

	  b.on('dragmove', () => {
		  i == 0 && spinFor(.85, 350);
	  });

	  b.on('mouseup', () => {
		  i == 1 && spinFor(0, 500)
	  });

	  b.on('mousedown', () => {
		  i == 2 && spinFor(0, 500)
	  });

	  layer.add(b);
	});

	stage.add(layer);
  
  setInterval(() => {
    const rotation = (new Date() - window.startTime)/10000*360;
    
    $("#clock").css("transform", "rotate(" + (rotation) +"deg)");
  }, 1000/60);
})
        </script>

    </body>
</html>
              
            
!

CSS

              
                body {
  margin: 0;
  padding: 0;
  overflow: hidden;
  background-color: #F0F0F0;
}
#output {
  position: absolute;
  top: 0;
  height: calc(100% - 6em);
  width: 27.5em;
  background: #00000008;
  overflow: scroll;
  font-size: xx-small;
}

#clock {
  pointer-events: none;
  position: absolute;
  top: 10px;
  right: 10px;
  width: 75px;
  height: 75px;
}
              
            
!

JS

              
                /// <reference path="./node_modules/zone.js/dist/zone.js.d.ts" />

const THRESHOLDS = [0, 17, 100, 250];
const EMPTY_MINUTE = {
  'blocks_0': 0,
  'time_0': 0,
  'blocks_17': 0,
  'time_17': 0,
  'blocks_100': 0,
  'time_100': 0,
  'blocks_250': 0,
  'time_250': 0,
};

type MinuteData = typeof EMPTY_MINUTE;
interface MinuteTag extends MinuteData {
  'tag': string;
}

type MinuteTags = Map<string, MinuteTag>;

class MinuteLogger {
  protected currentMinuteTags: MinuteTags = new Map<string, MinuteTag>();
  protected currentMinute: MinuteData = this.getEmptyMinute();

  public logJavascriptExecution(start: number, end: number, tags: string[]) {
    const elapsed = end - start;

    // Record the JavaScript time and tags.
    const microElapsed = Math.round(elapsed * 1000);
    tags.forEach((tag) => {
      if (!this.currentMinuteTags.has(tag)) {
        this.currentMinuteTags.set(
            tag, Object.assign({'tag': tag}, this.getEmptyMinute()));
      }
      let tagData = this.currentMinuteTags.get(tag)!;

      // Increment the block count and elapsed time for the tag.
      THRESHOLDS.forEach(t => {
        if (elapsed >= t) {
          tagData[`blocks_${t}`]++;
          tagData[`time_${t}`] += microElapsed;
        }
      });
    });

    // Increment the block count and elapsed time for the current minute.
    THRESHOLDS.forEach(t => {
      if (elapsed >= t) {
        this.currentMinute[`blocks_${t}`]++;
        this.currentMinute[`time_${t}`] += microElapsed;
      }
    });
  }

  public send() {
    if (this.currentMinute.time_0 > 0) {
      console.clear();
      const tags = Array.from(this.currentMinuteTags.entries()).sort(([k1, v1], [
                                                                       k2, v2
                                                                     ]) => {
        return v1.time_0 - v2.time_0;
      });
      tags.forEach(([k, v]) => {
          console.log(JSON.stringify(v, undefined, 2));
      });
      console.log('\n----- Aggregated Tags -----');
      console.log('MINUTE_TAG', JSON.stringify(this.currentMinute, undefined, 2));
    }

    // Reset the current minute data, tags, interactive flag, etc.
    this.currentMinute = this.getEmptyMinute();
    this.currentMinuteTags.clear();
  }

  private getEmptyMinute() {
    return Object.assign({}, EMPTY_MINUTE);
  }
}

class ZoneTracker {
  private tags: Set<string> = new Set();

  // The number of tasks that are on the stack.
  private taskDepth: number = 0;

  // The number of tasks that have been executed.
  private _totalTaskCount: number = 0;

  // // The sources of the tasks on the stack.
  private taskSourceStack: string[] = ['untracked'];
  private zoneNameStack: string[] = [''];
  private timer: () => number;
  private startTime: number = 0;
  private endTime: number = 0;
  private untaggedTag: string = '??';
  private currentMinuteStarted = -1e9;

  private readonly interval =
      10 * 1000;  // 10 seconds (normally we do 60 seconds)

  constructor(
      public minuteLogger: MinuteLogger,
  ) {
    if (!Zone || !Zone.current) {
      throw new Error(
          'Zone is not available! (Include Zone before running this script.)');
    }

    // Use the more accurate performance timer, if available.
    const perf = window.performance;
    this.timer = perf ? perf.now.bind(perf) : Date.now.bind(Date);

    // When we hook the zone, we are already running in a zone from
    // onInvokeTask, so we missed incrementing the task depth. Any tags called
    // from code invoked before we hooked the zone end up untracked.
    this.taskSourceStack[0] = '<time-not-tracked>';

    const zoneSpec = {
      onInvoke: this.onInvoke.bind(this),
      onInvokeTask: this.onInvokeTask.bind(this)
    };

    // Get the root zone.
    const zone = (function getRootZone(zone): Zone {
      return zone.parent ? getRootZone(zone) : zone;
    })(Zone.current);

    // Patch the root zone with our new zone spec. Clear the parent zone
    // delegate to avoid double releasing counts.
    const oldZoneDelegate = (zone as any)['_zoneDelegate'];
    let newZoneDelegate =
        new oldZoneDelegate.constructor(zone, oldZoneDelegate, zoneSpec);

    newZoneDelegate['_taskCounts'] =
        Object.assign({}, oldZoneDelegate['_taskCounts']);

    newZoneDelegate._parentDelegate = null;
    (zone as any)['_zoneDelegate'] = newZoneDelegate;
  }

  public addTag(tag: string) {
    const isUntagged = tag === this.untaggedTag;
    if (isUntagged) {
      const taskSource = this.taskSourceStack[this.taskDepth];
      if (taskSource) {
        tag += '(' + taskSource + ')';
      }
    }

    // Prepend the zone name.
    // (For instance, this could be NgZone in an Angular application)
    const zoneName = this.zoneNameStack[this.taskDepth];
    if (zoneName != '' && zoneName != '<root>') {
      tag = zoneName + '@' + tag;
    }

    // Add the tag to our internal set.
    this.tags.add(tag);
  }

  public get totalTaskCount(): number {
    return this._totalTaskCount;
  }

  /**
   * Start timing a section of code.
   * Can be nested but should not be interleaved.
   * @param source A description of the source that's starting this.
   * @param isMicroTask Whether or not this is a microTask. Used to group
   * microTask time with their associated tasks.
   */
  private startTiming(source: string, zoneName: string, isMicroTask: boolean) {
    // Increment the depth.
    this.taskDepth++;

    // We want the time for each task plus any associated microTasks. Zone
    // processing can be nested. We time the top-level zone and include any
    // nested zones in this time. Zones finish the task before processing
    // microTasks, so if this is a microTask, we don't want to actually start
    // the timer but instead keep the time from when we started the macroTask or
    // eventTask it's associated with.
    if (this.taskDepth == 1 && !isMicroTask) {
      // We are starting a new task timing.
      // Don't record if the start time hasn't been set. This
      // happens the first time in here.
      if (this.startTime > 0) {
        // If the last task wasn't tagged, add an 'untagged' tag so we get the
        // task source.
        if (this.tags.size == 0) {
          this.addTag(this.untaggedTag);
        }
        const tags = Array.from(this.tags);

        if ((this.endTime - this.currentMinuteStarted) > this.interval) {
          this.minuteLogger.send();
          this.currentMinuteStarted = this.startTime;
          window.startTime = new Date();
        }

        this.minuteLogger.logJavascriptExecution(
            this.startTime, this.endTime, tags);
        this.tags.clear();
      }

      this.startTime = this.timer();
    }

    // Record the task source and name. We'll use it when adding the tags.
    this.taskSourceStack[this.taskDepth] = source;
    this.zoneNameStack[this.taskDepth] = zoneName;
  }

  private stopTiming() {
    // Decrement the depth and if we reach 0 and there aren't any microtasks,
    // we've completed a run.
    if (this.taskDepth > 0) {
      if (--this.taskDepth == 0) {
        this.endTime = this.timer();
      }
    } else {
      console.warn('taskDepth mismatch');
    }
  }

  private addTagFromTaggedFn(callback: Function) {
    const tag = callback.name;

    if (tag) {
      this.addTag(tag);
    }
  }

  /**
   * Called when a zone is entered by calling zone.run().
   * @param parentZoneDelegate Delegate which performs the parent [ZoneSpec]
   * operation.
   * @param currentZone The current [Zone] where the current interceptor has
   * been declared.
   * @param targetZone The [Zone] which originally received the request.
   * @param callback The argument passed into the `run` method.
   * @param applyThis The argument passed into the `run` method.
   * @param applyArgs The argument passed into the `run` method.
   * @param source The argument passed into the `run` method.
   */
  onInvoke(
      parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone,
      callback: Function, applyThis: any, applyArgs: any[], source: string) {
    this.startTiming(source, targetZone.name, false);

    this.addTagFromTaggedFn(callback);

    try {
      return parentZoneDelegate.invoke(
          targetZone, callback, applyThis, applyArgs, source);
    } finally {
      this.stopTiming();
    }
  }

  /**
   * Called by Zone when it executes an asynchronous task.
   */
  onInvokeTask(
      parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone,
      task: Task, applyThis: any, applyArgs: any) {
    this.startTiming(task.source, targetZone.name, task.type == 'microTask');

    // Increment the total task count.
    this._totalTaskCount++;

    this.addTagFromTaggedFn(task.callback);

    try {
      return task.callback.apply(applyThis, applyArgs);
    } finally {
      this.stopTiming();
    }
  }
}

new ZoneTracker(new MinuteLogger());
              
            
!
999px

Console