User:Bohye Woo/labour experiment: Difference between revisions
No edit summary |
|||
Line 344: | Line 344: | ||
[[File:Bohye-woo-mouse-tracker-01.png|800px|thumbnail|right| Labour Experiment #2: Tracking mouse movement on web.]] | [[File:Bohye-woo-mouse-tracker-01.png|800px|thumbnail|right| Labour Experiment #2: Tracking mouse movement on web.]] | ||
<source lang="javascript"> | |||
<script type="text/javascript"> | |||
/*! | |||
* Mus.js v1.1.0 | |||
* (c) 2018 Mauricio Giordano <giordano@inevent.us> - InEvent | |||
* Released under the MIT License. | |||
*/ | |||
(function (global, factory) { | |||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : | |||
typeof define === 'function' && define.amd ? define(factory) : | |||
(global.Mus = factory()); | |||
}(this, (function () { 'use strict'; | |||
// Mus default cursor icon based on OSx default cursor | |||
var cursorIcon = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiCSB2aWV3Qm94PSIwIDAgMjggMjgiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI4IDI4IiB4bWw6c3BhY2U9InByZXNlcnZlIj48cG9seWdvbiBmaWxsPSIjRkZGRkZGIiBwb2ludHM9IjguMiwyMC45IDguMiw0LjkgMTkuOCwxNi41IDEzLDE2LjUgMTIuNiwxNi42ICIvPjxwb2x5Z29uIGZpbGw9IiNGRkZGRkYiIHBvaW50cz0iMTcuMywyMS42IDEzLjcsMjMuMSA5LDEyIDEyLjcsMTAuNSAiLz48cmVjdCB4PSIxMi41IiB5PSIxMy42IiB0cmFuc2Zvcm09Im1hdHJpeCgwLjkyMjEgLTAuMzg3MSAwLjM4NzEgMC45MjIxIC01Ljc2MDUgNi41OTA5KSIgd2lkdGg9IjIiIGhlaWdodD0iOCIvPjxwb2x5Z29uIHBvaW50cz0iOS4yLDcuMyA5LjIsMTguNSAxMi4yLDE1LjYgMTIuNiwxNS41IDE3LjQsMTUuNSAiLz48L3N2Zz4='; | |||
/** | |||
* Mus constructor that defines initial variables and | |||
* sets browser width and height. | |||
* @knownbug: if user decides to change browser window size on-the-go | |||
* it may cause bugs during playback | |||
*/ | |||
function Mus() { | |||
if (this === undefined) { | |||
console.error('Have you initialized Mus with "new" statement? (i.e. var mus = new Mus())'); | |||
return; | |||
} | |||
this.frames = []; | |||
this.timeouts = []; | |||
this.pos = 0; | |||
this.currPos = 0; | |||
this.startedAt = 0; | |||
this.finishedAt = 0; | |||
this.timePoint = false; | |||
this.recording = false; | |||
this.playing = false; | |||
this.playbackSpeed = this.speed.NORMAL; | |||
this.window = { | |||
width : window.outerWidth, | |||
height : window.outerHeight | |||
}; | |||
// Stores initial listeners | |||
this.onmousemove = window.onmousemove; | |||
this.onmousedown = window.onmousedown; | |||
this.onscroll = window.onscroll; | |||
}; | |||
/** | |||
* Here goes all Mus magic | |||
*/ | |||
Mus.prototype = { | |||
/** Mus Listeners **/ | |||
/** | |||
* Listener intended to be used with onmousemove | |||
* @param callback function a callback fnc | |||
* @return function the mouse move listener | |||
*/ | |||
moveListener : function(callback) { | |||
return function(e) { | |||
if (callback) callback(['m', e.clientX, e.clientY]); | |||
} | |||
}, | |||
/** | |||
* Listener intended to be used with onmousedown | |||
* @param callback function a callback fnc | |||
* @return function the mouse click listener | |||
*/ | |||
clickListener : function(callback) { | |||
return function(e) { | |||
if (callback) callback(['c', e.clientX, e.clientY]); | |||
} | |||
}, | |||
/** | |||
* Listener intended to be used with onscroll | |||
* @param callback function a callback fnc | |||
* @return function the window scroll listener | |||
*/ | |||
scrollListener : function(callback) { | |||
return function(e) { | |||
if (callback) callback(['s', document.scrollingElement.scrollLeft, document.scrollingElement.scrollTop]); | |||
} | |||
}, | |||
/** Mus recording tools **/ | |||
/** | |||
* Starts screen recording | |||
*/ | |||
record : function(onFrame) { | |||
if (this.recording) return; | |||
var self = this; | |||
if (self.startedAt == 0) self.startedAt = new Date().getTime() / 1000; | |||
// Sets initial scroll position of the window | |||
self.frames.push(['s', document.scrollingElement.scrollLeft, document.scrollingElement.scrollTop]); | |||
// Defines Mus listeners on window | |||
window.onmousemove = this.moveListener(function(pos) { | |||
self.frames.push(self.timePoint ? pos.concat(new Date().getTime() - (self.startedAt * 1000)) : pos); | |||
if (onFrame instanceof Function) onFrame(); | |||
}); | |||
window.onmousedown = this.clickListener(function(click) { | |||
self.frames.push(self.timePoint ? click.concat(new Date().getTime() - (self.startedAt * 1000)) : click); | |||
if (onFrame instanceof Function) onFrame(); | |||
}); | |||
window.onscroll = this.scrollListener(function(scroll) { | |||
self.frames.push(self.timePoint ? scroll.concat(new Date().getTime() - (self.startedAt * 1000)) : scroll); | |||
if (onFrame instanceof Function) onFrame(); | |||
}); | |||
// Sets our recording flag | |||
self.recording = true; | |||
}, | |||
/** | |||
* Stops screen recording | |||
*/ | |||
stop : function() { | |||
this.finishedAt = new Date().getTime() / 1000; | |||
window.onmousemove = this.onmousemove; | |||
window.onmousedown = this.onmousedown; | |||
window.onscroll = this.onscroll; | |||
// Sets our recording flag | |||
this.timeouts = []; | |||
this.recording = false; | |||
this.playing = false; | |||
this.pos = 0; | |||
}, | |||
/** | |||
* Pauses current execution | |||
*/ | |||
pause : function() { | |||
if (this.playing) { | |||
this.pos = this.currPos; | |||
this.playing = false; | |||
this.clearTimeouts(); | |||
} | |||
}, | |||
/** | |||
* Runs a playback of a recording | |||
* @param function onfinish a callback function | |||
*/ | |||
play : function(onfinish) { | |||
if (this.playing) return; | |||
var self = this; | |||
self.createCursor(); | |||
var node = document.getElementById("musCursor"); | |||
for (; self.pos < self.frames.length; self.pos++) { | |||
var delay = self.frames[self.pos].length > 3 ? | |||
self.frames[self.pos][3] : self.pos * self.playbackSpeed; | |||
self.timeouts.push(setTimeout(function(pos) { | |||
// Plays specific timeout | |||
self.playFrame(self, self.frames[pos], node); | |||
self.currPos = pos; | |||
if (pos == self.frames.length - 1) { | |||
node.style.backgroundColor = "transparent"; | |||
self.timeouts = []; | |||
self.playing = false; | |||
self.pos = 0; | |||
if (onfinish) onfinish(); | |||
} | |||
}, delay, self.pos)); | |||
}; | |||
this.playing = true; | |||
}, | |||
/** | |||
* Releases Mus instance | |||
*/ | |||
release : function() { | |||
this.frames = []; | |||
this.startedAt = 0; | |||
this.finishedAt = 0; | |||
this.stop(); | |||
this.destroyCursor(); | |||
this.destroyClickSnapshot(); | |||
}, | |||
/** Mus internal functions **/ | |||
/** | |||
* Play a specific frame from playback | |||
*/ | |||
playFrame : function(self, frame, node) { | |||
if (frame[0] == 'm') { | |||
node.style.left = self.getXCoordinate(frame[1]) + "px"; | |||
node.style.top = self.getYCoordinate(frame[2]) + "px"; | |||
} else if (frame[0] == 'c') { | |||
self.createClickSnapshot(frame[2], frame[1]); | |||
} else if (frame[0] == 's') { | |||
window.scrollTo(frame[1], frame[2]); | |||
} | |||
}, | |||
/** | |||
* Clears all timeouts stored | |||
*/ | |||
clearTimeouts : function() { | |||
for (var i in this.timeouts) { | |||
clearTimeout(this.timeouts[i]); | |||
} | |||
this.timeouts = []; | |||
}, | |||
/** | |||
* Calculates time elapsed during recording | |||
* @return integer time elapsed | |||
*/ | |||
timeElapsed : function() { | |||
return this.finishedAt - this.startedAt; | |||
}, | |||
/** | |||
* Creates Mus cursor if non-existent | |||
*/ | |||
createCursor : function() { | |||
if (!document.getElementById("musCursor")) { | |||
var node = document.createElement("div"); | |||
node.id = "musCursor"; | |||
node.style.position = "fixed"; | |||
node.style.width = "32px"; | |||
node.style.height = "32px"; | |||
node.style.top = "-100%"; | |||
node.style.left = "-100%"; | |||
node.style.borderRadius = "32px"; | |||
node.style.backgroundImage = "url(" + cursorIcon + ")"; | |||
document.body.appendChild(node); | |||
} | |||
}, | |||
/** | |||
* Destroys Mus cursor | |||
*/ | |||
destroyCursor : function() { | |||
var cursor = document.getElementById("musCursor"); | |||
if (cursor) cursor.remove(); | |||
}, | |||
/** | |||
* Creates Mus click snapshot | |||
*/ | |||
createClickSnapshot : function(x, y) { | |||
var left = document.scrollingElement.scrollLeft; | |||
var top = document.scrollingElement.scrollTop; | |||
var node = document.createElement("div"); | |||
node.className = "musClickSnapshot"; | |||
node.style.position = "absolute"; | |||
node.style.width = "32px"; | |||
node.style.height = "32px"; | |||
node.style.top = (x + top) + "px"; | |||
node.style.left = (y + left) + "px"; | |||
node.style.borderRadius = "32px"; | |||
node.style.backgroundColor = "red"; | |||
node.style.opacity = 0.2; | |||
document.body.appendChild(node); | |||
}, | |||
/** | |||
* Destroys Mus click snapshot | |||
*/ | |||
destroyClickSnapshot : function() { | |||
var nodes = document.getElementsByClassName("musClickSnapshot"); | |||
while (nodes.length > 0) { | |||
nodes[0].remove(); | |||
} | |||
}, | |||
/** | |||
* Calculates current X coordinate of mouse based on window dimensions provided | |||
* @param x integer the x position | |||
* @return integer calculated x position | |||
*/ | |||
getXCoordinate : function(x) { | |||
if (window.outerWidth > this.window.width) { | |||
return parseInt(this.window.width * x / window.outerWidth); | |||
} | |||
return parseInt(window.outerWidth * x / this.window.width); | |||
}, | |||
/** | |||
* Calculates current Y coordinate of mouse based on window dimensions provided | |||
* @param y integer the y position | |||
* @return integer calculated y position | |||
*/ | |||
getYCoordinate : function(y) { | |||
if (window.outerHeight > this.window.height) { | |||
return parseInt(this.window.height * y / window.outerHeight); | |||
} | |||
return parseInt(window.outerHeight * y / this.window.height); | |||
}, | |||
/** Public getters and setters **/ | |||
/** | |||
* Get all generated Mus data | |||
* @return array generated Mus data | |||
*/ | |||
getData : function() { | |||
return { | |||
frames : this.frames, | |||
timeElapsed : this.timeElapsed(), | |||
window : { | |||
width : window.outerWidth, | |||
height : window.outerHeight | |||
} | |||
}; | |||
}, | |||
/** | |||
* Get point time recording flag | |||
* @return boolean point time flag | |||
*/ | |||
isTimePoint : function() { | |||
return this.timePoint; | |||
}, | |||
/** | |||
* Sets generated Mus data for playback | |||
* @param data array generated Mus data | |||
*/ | |||
setData : function(data) { | |||
if (data.frames) this.frames = data.frames; | |||
if (data.window) this.window = data.window; | |||
}, | |||
/** | |||
* Sets recorded frames for playback | |||
* @param frames array the frames array | |||
*/ | |||
setFrames : function(frames) { | |||
this.frames = frames; | |||
}, | |||
/** | |||
* Sets custom window size for playback | |||
* @param width integer window width | |||
* @param height integer window height | |||
*/ | |||
setWindowSize : function(width, height) { | |||
this.window.width = width; | |||
this.window.height = height; | |||
}, | |||
/** | |||
* Sets a playback speed based on Mus speed set | |||
* @param speed integer the playback speed | |||
*/ | |||
setPlaybackSpeed : function(speed) { | |||
this.playbackSpeed = speed; | |||
}, | |||
/** | |||
* Sets point time recording for accurate data | |||
* @param | |||
*/ | |||
setTimePoint : function(timePoint) { | |||
this.timePoint = timePoint; | |||
}, | |||
/** | |||
* Informs if Mus is currently recording | |||
* @return boolean is recording? | |||
*/ | |||
isRecording : function() { | |||
return this.recording; | |||
}, | |||
/** | |||
* Informs if Mus is currently playing | |||
* @return boolean is playing? | |||
*/ | |||
isPlaying : function() { | |||
return this.playing; | |||
}, | |||
/** Mus speed constants **/ | |||
speed : { | |||
SLOW : 35, | |||
NORMAL : 15, | |||
FAST : 5 | |||
} | |||
}; | |||
return Mus; | |||
}))); | |||
</script> | |||
</source> |
Revision as of 13:46, 4 December 2019
Labour investigation in social media
This is a small prototyping with my personal data created from Facebook and Instagram. I call this experiment as a 'crime scene case' in which I will make an investigation report, it might lead me to start on a new study case. By downloading my personal datas I produced, I would like to delve into investigate what kinds of data I have produced, To create this data what labour is being used, how many time I worked to create them. I will visualize them to see the possibilities of materializing the labour.
Step 1: Extracting && Analyzing my data
What data has been collected? I've collected my 9 years of social media experience in JSON file.
A JSON file that collected my likes on posts
{
"reactions": [
{
"timestamp": 1569322034,
"data": [
{
"reaction": {
"reaction": "LIKE",
"actor": "Bo Woopsie"
}
}
],
"title": "Bo Woopsie likes Roosje Klap's post."
},
{
"timestamp": 1568990971,
"data": [
{
"reaction": {
"reaction": "LIKE",
"actor": "Bo Woopsie"
}
}
],
"title": "Bo Woopsie likes Shinyoung Kim's photo."
},
{
"timestamp": 1568757503,
"data": [
{
"reaction": {
"reaction": "LIKE",
"actor": "Bo Woopsie"
}
}
],
"title": "Bo Woopsie likes Cramer Florian's album: Public Library | Latag."
},
{
"timestamp": 1567672802,
"data": [
{
"reaction": {
"reaction": "LIKE",
"actor": "Bo Woopsie"
}
}
],
"title": "Bo Woopsie likes Michel Hoogervorst's photo."
},
]
}
Step 2: Visualizing my labour
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Let's publish our labour</title>
<style type="text/css">
svg {
border: 1px solid gray;
}
circle.item {
fill: green;
}
</style>
</head>
<body>
<div id="content"></div>
<svg id="graph" width=5000 height=100></svg>
</body>
<script src="d3/d3.min.js"></script>
<script type="text/javascript">
d3.json ("posts_and_comments.json").then(data => {
console.log("data", data)
let start = d3.min(data.reactions, d=>d.timestamp),
end = d3.max(data.reactions, d=>d.timestamp);
console.log("min", start, "max", end);
let pos = d3.scaleLinear().domain([start, end]).range([0, 5000]);
window.pos = pos;
d3.select("#graph")
.selectAll(".item")
.data(data.reactions)
.enter()
.append("circle")
.attr("cx", d => pos(d.timestamp))
.attr("cy", 10)
.attr("r", 2)
.attr("class", "item")
.append("title")
.text(d => d.title);
//same as: .text(function(d) { return d.title });
})
</script>
</html>
Step 3: Adding more datas to compare
Added 'other_peoples_posts_to_your_timeline.json' & 'likes_on_external_sites.json'
<script type="text/javascript">
d3.json ("posts_and_comments.json").then(data => {
console.log("data", data)
let start = d3.min(data.reactions, d=>d.timestamp),
end = d3.max(data.reactions, d=>d.timestamp);
console.log("min", start, "max", end);
let pos = d3.scaleLinear().domain([start, end]).range([0, 5000]);
window.pos = pos;
d3.select("#graph")
.selectAll(".item")
.data(data.reactions)
.enter()
.append("rect")
.attr("x", d => pos(d.timestamp))
.attr("y", 0)
.attr("width", 2)
.attr("height", 2)
.attr("class", "item")
.append("title")
.text(d => d.title)
//same as: .text(function(d) { return d.title });
})
d3.json ("likes_on_external_sites.json").then(data => {
console.log("data", data)
let start = d3.min(data.other_likes, d=>d.timestamp),
end = d3.max(data.other_likes, d=>d.timestamp);
console.log("min", start, "max", end);
let pos = d3.scaleLinear().domain([start, end]).range([0, 5000]);
window.pos = pos;
d3.select("#graph")
.selectAll(".item2")
.data(data.other_likes)
.enter()
.append("circle")
.attr("cx", d => pos(d.timestamp))
.attr("cy", 10)
.attr("r", 2)
.attr("class", "item2")
.append("title")
.text(d => d.title);
})
d3.json ("other_peoples_posts_to_your_timeline.json").then(data => {
data = data.wall_posts_sent_to_you
console.log("data3", data)
let start = d3.min(data.activity_log_data, d=>d.timestamp),
end = d3.max(data.activity_log_data, d=>d.timestamp);
console.log("min", start, "max", end);
let pos = d3.scaleLinear().domain([start, end]).range([0, 5000]);
window.pos = pos;
d3.select("#graph")
.selectAll(".item3")
.data(data.activity_log_data)
.enter()
.append("circle")
.attr("cx", d => pos(d.timestamp))
.attr("cy", 15)
.attr("r", 2)
.attr("class", "item3")
.append("title")
.text(d => d.title);
})
</script>
Step 4: Writing into a new style of Javascript
I loaded different JSON files every time. What is interesting to see is to see the whole timeline of my labour. Problem of this way of coding is that they're stretched and not in a same scale. To make them in a same timeline, I need to find total minimum and maximam. Therefore I re-write with a new style of Javascript that allows you to write linearly.
New Javascript terms
- Promises
The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value. instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future.
- Async
Async is a utility module which provides straight-forward, powerful functions for working with asynchronous JavaScript.
- Await
The await operator is used to wait for a Promise.
<script type="text/javascript">
async function loaddata () {
var data1 = await d3.json ("posts_and_comments.json"),
data2 = await d3.json ("likes_on_external_sites.json"),
data3 = await d3.json ("other_peoples_posts_to_your_timeline.json");
console.log("loaded", data1, data2, data3);
let start1 = d3.min(data1.reactions, d=>d.timestamp),
end1 = d3.max(data1.reactions, d=>d.timestamp);
console.log("1 min", start1, "max", end1);
let start2 = d3.min(data2.other_likes, d=>d.timestamp),
end2 = d3.max(data2.other_likes, d=>d.timestamp);
console.log("2 min", start2, "max", end2);
data3 = data3.wall_posts_sent_to_you;
let start3 = d3.min(data3.activity_log_data, d=>d.timestamp),
end3 = d3.max(data3.activity_log_data, d=>d.timestamp);
console.log("3 min", start3, "max", end3);
let start = Math.min(start1, start2, start3),
end = Math.max(end1, end2, end3);
let pos = d3.scaleLinear().domain([start, end]).range([0, 5000]);
window.pos = pos;
d3.select("#graph")
.selectAll(".item")
.data(data1.reactions)
.enter()
.append("rect")
.attr("x", d => pos(d.timestamp))
.attr("y", 0)
.attr("width", 2)
.attr("height", 2)
.attr("class", "item")
.append("title")
.text(d => d.title)
//same as: .text(function(d) { return d.title });
d3.select("#graph")
.selectAll(".item2")
.data(data2.other_likes)
.enter()
.append("circle")
.attr("cx", d => pos(d.timestamp))
.attr("cy", 10)
.attr("r", 2)
.attr("class", "item2")
.append("title")
.text(d => d.title);
d3.select("#graph")
.selectAll(".item3")
.data(data3.activity_log_data)
.enter()
.append("circle")
.attr("cx", d => pos(d.timestamp))
.attr("cy", 15)
.attr("r", 2)
.attr("class", "item3")
.append("title")
.text(d => d.title);
}
loaddata()
</script>
Step 5: Creating a list of timestamp: legend
<script type="text/javascript">
let start_date = new Date(start*1000),
end_date = new Date(end*1000);
console.log(start_date, end_date);
let legend_dates = [];
for (let year = start_date.getYear(); year <= end_date.getYear(); year ++) {
legend_dates.push(new Date(1900+year, 0, 1));
}
console.log("legend dates", legend_dates)
d3.select("#graph")
.selectAll("text.legend")
.data(legend_dates)
.enter()
.append("text")
.attr("class", "legend")
.text(d => d.toString());
}
loaddata()
</script>
outcome
ex) January 1 in each year
legend dates
Array (10)
0 Fri Jan 01 2010 00:00:00 GMT+0100 (CET)
1 Sat Jan 01 2011 00:00:00 GMT+0100 (CET)
2 Sun Jan 01 2012 00:00:00 GMT+0100 (CET)
3 Tue Jan 01 2013 00:00:00 GMT+0100 (CET)
4 Wed Jan 01 2014 00:00:00 GMT+0100 (CET)
5 Thu Jan 01 2015 00:00:00 GMT+0100 (CET)
6 Fri Jan 01 2016 00:00:00 GMT+0100 (CET)
7 Sun Jan 01 2017 00:00:00 GMT+0100 (CET)
8 Mon Jan 01 2018 00:00:00 GMT+0100 (CET)
9 Tue Jan 01 2019 00:00:00 GMT+0100 (CET)
==
Labour investigation in digital activity
A second experiment on visualising free labour from digital activities, in this case, mouse movement on the web. This tool recognizes the mouse movement, when it's clicked, and where with the coordination.
<script type="text/javascript">
/*!
* Mus.js v1.1.0
* (c) 2018 Mauricio Giordano <giordano@inevent.us> - InEvent
* Released under the MIT License.
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.Mus = factory());
}(this, (function () { 'use strict';
// Mus default cursor icon based on OSx default cursor
var cursorIcon = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiCSB2aWV3Qm94PSIwIDAgMjggMjgiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI4IDI4IiB4bWw6c3BhY2U9InByZXNlcnZlIj48cG9seWdvbiBmaWxsPSIjRkZGRkZGIiBwb2ludHM9IjguMiwyMC45IDguMiw0LjkgMTkuOCwxNi41IDEzLDE2LjUgMTIuNiwxNi42ICIvPjxwb2x5Z29uIGZpbGw9IiNGRkZGRkYiIHBvaW50cz0iMTcuMywyMS42IDEzLjcsMjMuMSA5LDEyIDEyLjcsMTAuNSAiLz48cmVjdCB4PSIxMi41IiB5PSIxMy42IiB0cmFuc2Zvcm09Im1hdHJpeCgwLjkyMjEgLTAuMzg3MSAwLjM4NzEgMC45MjIxIC01Ljc2MDUgNi41OTA5KSIgd2lkdGg9IjIiIGhlaWdodD0iOCIvPjxwb2x5Z29uIHBvaW50cz0iOS4yLDcuMyA5LjIsMTguNSAxMi4yLDE1LjYgMTIuNiwxNS41IDE3LjQsMTUuNSAiLz48L3N2Zz4=';
/**
* Mus constructor that defines initial variables and
* sets browser width and height.
* @knownbug: if user decides to change browser window size on-the-go
* it may cause bugs during playback
*/
function Mus() {
if (this === undefined) {
console.error('Have you initialized Mus with "new" statement? (i.e. var mus = new Mus())');
return;
}
this.frames = [];
this.timeouts = [];
this.pos = 0;
this.currPos = 0;
this.startedAt = 0;
this.finishedAt = 0;
this.timePoint = false;
this.recording = false;
this.playing = false;
this.playbackSpeed = this.speed.NORMAL;
this.window = {
width : window.outerWidth,
height : window.outerHeight
};
// Stores initial listeners
this.onmousemove = window.onmousemove;
this.onmousedown = window.onmousedown;
this.onscroll = window.onscroll;
};
/**
* Here goes all Mus magic
*/
Mus.prototype = {
/** Mus Listeners **/
/**
* Listener intended to be used with onmousemove
* @param callback function a callback fnc
* @return function the mouse move listener
*/
moveListener : function(callback) {
return function(e) {
if (callback) callback(['m', e.clientX, e.clientY]);
}
},
/**
* Listener intended to be used with onmousedown
* @param callback function a callback fnc
* @return function the mouse click listener
*/
clickListener : function(callback) {
return function(e) {
if (callback) callback(['c', e.clientX, e.clientY]);
}
},
/**
* Listener intended to be used with onscroll
* @param callback function a callback fnc
* @return function the window scroll listener
*/
scrollListener : function(callback) {
return function(e) {
if (callback) callback(['s', document.scrollingElement.scrollLeft, document.scrollingElement.scrollTop]);
}
},
/** Mus recording tools **/
/**
* Starts screen recording
*/
record : function(onFrame) {
if (this.recording) return;
var self = this;
if (self.startedAt == 0) self.startedAt = new Date().getTime() / 1000;
// Sets initial scroll position of the window
self.frames.push(['s', document.scrollingElement.scrollLeft, document.scrollingElement.scrollTop]);
// Defines Mus listeners on window
window.onmousemove = this.moveListener(function(pos) {
self.frames.push(self.timePoint ? pos.concat(new Date().getTime() - (self.startedAt * 1000)) : pos);
if (onFrame instanceof Function) onFrame();
});
window.onmousedown = this.clickListener(function(click) {
self.frames.push(self.timePoint ? click.concat(new Date().getTime() - (self.startedAt * 1000)) : click);
if (onFrame instanceof Function) onFrame();
});
window.onscroll = this.scrollListener(function(scroll) {
self.frames.push(self.timePoint ? scroll.concat(new Date().getTime() - (self.startedAt * 1000)) : scroll);
if (onFrame instanceof Function) onFrame();
});
// Sets our recording flag
self.recording = true;
},
/**
* Stops screen recording
*/
stop : function() {
this.finishedAt = new Date().getTime() / 1000;
window.onmousemove = this.onmousemove;
window.onmousedown = this.onmousedown;
window.onscroll = this.onscroll;
// Sets our recording flag
this.timeouts = [];
this.recording = false;
this.playing = false;
this.pos = 0;
},
/**
* Pauses current execution
*/
pause : function() {
if (this.playing) {
this.pos = this.currPos;
this.playing = false;
this.clearTimeouts();
}
},
/**
* Runs a playback of a recording
* @param function onfinish a callback function
*/
play : function(onfinish) {
if (this.playing) return;
var self = this;
self.createCursor();
var node = document.getElementById("musCursor");
for (; self.pos < self.frames.length; self.pos++) {
var delay = self.frames[self.pos].length > 3 ?
self.frames[self.pos][3] : self.pos * self.playbackSpeed;
self.timeouts.push(setTimeout(function(pos) {
// Plays specific timeout
self.playFrame(self, self.frames[pos], node);
self.currPos = pos;
if (pos == self.frames.length - 1) {
node.style.backgroundColor = "transparent";
self.timeouts = [];
self.playing = false;
self.pos = 0;
if (onfinish) onfinish();
}
}, delay, self.pos));
};
this.playing = true;
},
/**
* Releases Mus instance
*/
release : function() {
this.frames = [];
this.startedAt = 0;
this.finishedAt = 0;
this.stop();
this.destroyCursor();
this.destroyClickSnapshot();
},
/** Mus internal functions **/
/**
* Play a specific frame from playback
*/
playFrame : function(self, frame, node) {
if (frame[0] == 'm') {
node.style.left = self.getXCoordinate(frame[1]) + "px";
node.style.top = self.getYCoordinate(frame[2]) + "px";
} else if (frame[0] == 'c') {
self.createClickSnapshot(frame[2], frame[1]);
} else if (frame[0] == 's') {
window.scrollTo(frame[1], frame[2]);
}
},
/**
* Clears all timeouts stored
*/
clearTimeouts : function() {
for (var i in this.timeouts) {
clearTimeout(this.timeouts[i]);
}
this.timeouts = [];
},
/**
* Calculates time elapsed during recording
* @return integer time elapsed
*/
timeElapsed : function() {
return this.finishedAt - this.startedAt;
},
/**
* Creates Mus cursor if non-existent
*/
createCursor : function() {
if (!document.getElementById("musCursor")) {
var node = document.createElement("div");
node.id = "musCursor";
node.style.position = "fixed";
node.style.width = "32px";
node.style.height = "32px";
node.style.top = "-100%";
node.style.left = "-100%";
node.style.borderRadius = "32px";
node.style.backgroundImage = "url(" + cursorIcon + ")";
document.body.appendChild(node);
}
},
/**
* Destroys Mus cursor
*/
destroyCursor : function() {
var cursor = document.getElementById("musCursor");
if (cursor) cursor.remove();
},
/**
* Creates Mus click snapshot
*/
createClickSnapshot : function(x, y) {
var left = document.scrollingElement.scrollLeft;
var top = document.scrollingElement.scrollTop;
var node = document.createElement("div");
node.className = "musClickSnapshot";
node.style.position = "absolute";
node.style.width = "32px";
node.style.height = "32px";
node.style.top = (x + top) + "px";
node.style.left = (y + left) + "px";
node.style.borderRadius = "32px";
node.style.backgroundColor = "red";
node.style.opacity = 0.2;
document.body.appendChild(node);
},
/**
* Destroys Mus click snapshot
*/
destroyClickSnapshot : function() {
var nodes = document.getElementsByClassName("musClickSnapshot");
while (nodes.length > 0) {
nodes[0].remove();
}
},
/**
* Calculates current X coordinate of mouse based on window dimensions provided
* @param x integer the x position
* @return integer calculated x position
*/
getXCoordinate : function(x) {
if (window.outerWidth > this.window.width) {
return parseInt(this.window.width * x / window.outerWidth);
}
return parseInt(window.outerWidth * x / this.window.width);
},
/**
* Calculates current Y coordinate of mouse based on window dimensions provided
* @param y integer the y position
* @return integer calculated y position
*/
getYCoordinate : function(y) {
if (window.outerHeight > this.window.height) {
return parseInt(this.window.height * y / window.outerHeight);
}
return parseInt(window.outerHeight * y / this.window.height);
},
/** Public getters and setters **/
/**
* Get all generated Mus data
* @return array generated Mus data
*/
getData : function() {
return {
frames : this.frames,
timeElapsed : this.timeElapsed(),
window : {
width : window.outerWidth,
height : window.outerHeight
}
};
},
/**
* Get point time recording flag
* @return boolean point time flag
*/
isTimePoint : function() {
return this.timePoint;
},
/**
* Sets generated Mus data for playback
* @param data array generated Mus data
*/
setData : function(data) {
if (data.frames) this.frames = data.frames;
if (data.window) this.window = data.window;
},
/**
* Sets recorded frames for playback
* @param frames array the frames array
*/
setFrames : function(frames) {
this.frames = frames;
},
/**
* Sets custom window size for playback
* @param width integer window width
* @param height integer window height
*/
setWindowSize : function(width, height) {
this.window.width = width;
this.window.height = height;
},
/**
* Sets a playback speed based on Mus speed set
* @param speed integer the playback speed
*/
setPlaybackSpeed : function(speed) {
this.playbackSpeed = speed;
},
/**
* Sets point time recording for accurate data
* @param
*/
setTimePoint : function(timePoint) {
this.timePoint = timePoint;
},
/**
* Informs if Mus is currently recording
* @return boolean is recording?
*/
isRecording : function() {
return this.recording;
},
/**
* Informs if Mus is currently playing
* @return boolean is playing?
*/
isPlaying : function() {
return this.playing;
},
/** Mus speed constants **/
speed : {
SLOW : 35,
NORMAL : 15,
FAST : 5
}
};
return Mus;
})));
</script>