Skip to content

Instantly share code, notes, and snippets.

@usirin
Created September 27, 2013 03:23
Show Gist options
  • Select an option

  • Save usirin/6723742 to your computer and use it in GitHub Desktop.

Select an option

Save usirin/6723742 to your computer and use it in GitHub Desktop.
(function () {
/**
* Constructor of LoggedIn SVG
* object. Inherits its properties from
* caq.raphael.Main object.
* @return {caq.raphael.LoggedIn} Main paper to work on.
*/
caq.raphael.LoggedIn = function() {
// start inheritance from the Main class.
caq.raphael.Main.apply(this, arguments);
this.animationDone = false;
/**
* Gap between small circles.
* @type {number}
*/
this.gapBetweenCircles = this.bigRadius.initial * 2;
/**
* Array like object holds both top and bottom
* background canvas.
* @type {Raphael.Paper.Set}
*/
this.backgroundCanvasSet = this.paper.set();
/**
* Array like object holds both top and bottom
* strokes.
* @type {Raphael.Paper.Set}
*/
this.strokeSet = this.paper.set();
/**
* Main text on top of main circle.
* @type {Raphael.Paper.Text}
*/
this.mainText;
/**
* Array like object holds both top and bottom
* main circle.
* @type {Raphael.Paper.Set}
*/
this.mainCircleSet = this.paper.set();
/**
* Array like object holds all of the small
* clickable circles.
* @type {Raphael.Paper.Set}
*/
this.smallCirclesSet = this.paper.set();
/**
* Array like object holds all of the small
* clickable circle texts.
* @type {Raphael.Paper.Set}
*/
this.smallCircleTextsSet = this.paper.set();
/**
* Array like object that holds the lines
* as stroke to the top and bottom canvas.
* @type {Raphael.Paper.Set}
*/
this.linesSet = this.paper.set();
/**
* Array like object that holds timer
* objects.
* @type {Raphael.Paper.Set}
*/
this.timersGroupSet = this.paper.set();
/**
* Array like object that holds timer
* objects.
* @type {Raphael.Paper.Set}
*/
this.timersSet = this.paper.set();
/**
* Array like object that holds both
* '+' button and the motivation text.
* @type {Raphael.Paper.Set}
*/
this.createGroupSet = this.paper.set();
/**
* Variable to understand if the
* firstAjax for countdowns received.
* @type {Boolean}
*/
this.firstAjaxReceived = false;
/**
* Variable to understand if the
* countdown timers is created.
* @type {Boolean}
*/
this.timersCreated = false;
/**
* Holds the date of the clicked
* capsule.
* @type {Date}
*/
this.countdown = new Date;
/**
* The `+` button.
* @type {Raphael.Paper.Text}
*/
this.addPostButton;
/**
* The text which stays above the
* addPostButton.
* @type {Raphael.Paper.Text}
*/
this.addPostMotivationText;
}
/**
* Alias for caq.raphael.LoggedIn
* @type {caq.raphael.LoggedIn}
*/
var LoggedIn = caq.raphael.LoggedIn;
LoggedIn.colors = {
DARK: '#2d3e50',
WHITE: '#ecf0f1',
BLUE: '#3598dc',
RED: '#e84c3d',
GREEN: '#2fcc71',
ORANGE: '#e77e23'
};
LoggedIn.capsuleNames = {
MONTHLY: 'M',
QUARTERLY: 'Q',
YEARLY: 'Y',
DECADE: 'D'
}
LoggedIn.prototype.init = function() {
var self = this;
setInterval(function() {
if(self.firstAjaxReceived) {
var ll = self.timersSet.length;
var timeStrings = self.msToTime(self.countdown - new Date)
for(var i = 0; i < ll; i++) {
self.timersSet[i].attr({text: timeStrings[i]});
}
}
}, 1000);
this.createBackgroundCanvas();
this.createStrokes();
this.createSmallCircles();
this.animateSmallCircles();
this.createMainCircle();
this.animateMainCircle();
this.createAddPostButton();
this.createAddPostMotivationText();
this.registerEvents();
};
/**
* Creates the '+' button and adds it
* to the papeer.
* @return {caq.raphael.LoggedIn}
*/
LoggedIn.prototype.createAddPostButton = function(opt_x, opt_y, opt_text) {
var x = opt_x || -50;
var y = opt_y || -50;
var text = opt_text || '+';
this.addPostButton = this.paper.text(-50, -50, text);
this.createGroupSet.push(this.addPostButton);
return this;
};
/**
* Creates a message and places is to the paper
* to motivate user to create a post.
* @param {String} opt_motivationText Message to be shown
* @return {caq.raphael.LoggedIn}
*/
LoggedIn.prototype.createAddPostMotivationText = function(opt_motivationText) {
var motivationText = opt_motivationText ||
'Have your place in today\'s history.\n' +
'Start telling your story...';
this.addPostMotivationText = this.paper.text(-500, -500, motivationText);
this.createGroupSet.push(this.addPostMotivationText);
return this;
};
/**
* Creates a background canvas, (SVG),
* from 2 different canvas,
* so we can create animations with them.
* @return {caq.raphael.LoggedIn}
*/
LoggedIn.prototype.createBackgroundCanvas = function() {
this.paper.setStart();
var topCanvas = this.paper.rect(this.x,
this.y,
this.width,
Math.ceil(this.height / 2));
var bottomCanvas = this.paper.rect(this.x,
Math.ceil(this.height / 2),
this.width,
Math.floor(this.height));
this.backgroundCanvasSet = this.paper.setFinish();
this.backgroundCanvasSet.attr({
fill: LoggedIn.colors.DARK,
'stroke-width': 0,
opacity: 0.75
});
return this;
};
/**
* Creates 2 different stroke
* as if they are the strokes of the
* main circle.
* @return {caq.raphael.LoggedIn}
*/
LoggedIn.prototype.createStrokes = function() {
var strokeWidth = this.bigRadius.initial * 0.1;
this.paper.setStart();
var topStroke = this.paper.path().attr({
reverseArc: [this.center.x, this.center.y, 0, 360, this.bigRadius.initial]
});
var bottomStroke = this.paper.path().attr({
arc: [this.center.x, this.center.y, 0, 360, this.bigRadius.initial]
});
this.strokeSet = this.paper.setFinish();
this.strokeSet.attr({"stroke-width": strokeWidth});
return this;
};
/**
* Creates the main big circle
* with the text in it.
* @return {caq.raphael.LoggedIn}
*/
LoggedIn.prototype.createMainCircle = function() {
var bigRadius = this.bigRadius.initial;
var smallRadius = this.smallRadius.initial;
var paper = this.paper;
paper.setStart();
var topCircle = this.sector(this.center.x, this.center.y, 1, 0, 180);
var bottomCircle = this.sector(this.center.x, this.center.y, 1, 180, 360);
this.mainCircleSet = paper.setFinish();
this.mainCircleSet.attr({fill: LoggedIn.colors.WHITE, 'stroke-width': 0});
this.mainText = paper.text(this.center.x, this.center.y, 'PAST\nSELF')
.attr({
'font-size': 1,
'font-family': this.fontFamily,
fill: LoggedIn.colors.DARK
});
return this;
};
/**
* Animates the main circle with thext.
* @param {number} opt_animationSpeed
* @return {void}
*/
LoggedIn.prototype.animateMainCircle = function(opt_animationSpeed) {
var animationSpeed = opt_animationSpeed || 1500;
var bigRadius = this.bigRadius.initial;
var smallRadius = this.smallRadius.initial;
var topCircle = this.mainCircleSet[0];
topCircle.animate({
path: this.getTransformPath(this.center.x, this.center.y, bigRadius, 0, 180)
}, animationSpeed, '<>');
var bottomCircle = this.mainCircleSet[1];
bottomCircle.animate({
path: this.getTransformPath(this.center.x, this.center.y, bigRadius, 180, 360)
}, animationSpeed, '<>');
this.mainText.animate({'font-size': smallRadius}, animationSpeed, '<>');
return this;
}
/**
* Creates the small circles we
* used as buttons.
* @param {number} opt_numberOfCircles
* @return {void}
*/
LoggedIn.prototype.createSmallCircles = function(opt_numberOfCircles) {
var numberOfCircles = opt_numberOfCircles || 4;
var smallRadius = this.smallRadius.initial;
var buttonColors = [
LoggedIn.colors.RED,
LoggedIn.colors.ORANGE,
LoggedIn.colors.GREEN,
LoggedIn.colors.BLUE
]
var buttonTexts = [
LoggedIn.capsuleNames.MONTHLY,
LoggedIn.capsuleNames.QUARTERLY,
LoggedIn.capsuleNames.YEARLY,
LoggedIn.capsuleNames.DECADE
];
var smallCirclesSet = this.paper.set();
var smallCircleTextsSet = this.paper.set();
// we need to create buttons and their texts
// regarding to a creation line,
// because we want the animations line
// to be different than the index
// numbers of the set. so we are using
// the creationLine variable to understand it.
var creationLine = [3, 0, 1, 2];
for (var j = 0, i = creationLine[j]; j < numberOfCircles; j++, i = creationLine[j]) {
// coordinate is like the center is (0, 0)
// therefore, the index number 0 will be -2
// 1 will be -1
// and 2 and 3 will be 1 and 2.
var coordinate = (i < numberOfCircles / 2) ? i - 2 : i - 1,
// we will need to shift the circles
// closest to the middle one.
offset = (coordinate !== -1 && coordinate !== 1) ? this.globalOffset : 0,
// we are positioning the circles like
// if they are negative, to the left
// positive to the right.
position = (coordinate < 0)
? this.center.x + (coordinate * this.gapBetweenCircles + offset)
: this.center.x + (coordinate * this.gapBetweenCircles - offset);
var button = this.paper.circle(this.center.x, this.center.y, 1)
.attr({fill: buttonColors[i], 'stroke-width': 0, cursor: 'pointer'})
.data('animate.x', position);
button.node.setAttribute('data-capsule-id', i + 1);
button.node.setAttribute('data-stroke-color', buttonColors[i]);
var text = this.paper.text(this.center.x, this.center.y, buttonTexts[i])
.attr({
'font-size': 1,
'font-family': this.fontFamily,
fill: LoggedIn.colors.DARK,
cursor: 'pointer',
opacity: 0
})
.data('animate.x', position);
text.node.setAttribute('data-capsule-id', i + 1);
text.node.setAttribute('data-stroke-color', buttonColors[i]);
// we are pushing the circles and the texts
// to a local set variable, so that
// we will be rearrange them and push it to
// the global sets in the correct order.
smallCirclesSet.push(button);
smallCircleTextsSet.push(text);
};
// take the necessary object from the
// local set, and push it to the global set
// in the correct order.
for (var j = 0, i = creationLine[j]; j < numberOfCircles; j++, i = creationLine[j]) {
this.smallCirclesSet.push(smallCirclesSet[i]);
this.smallCircleTextsSet.push(smallCircleTextsSet[i]);
}
};
/**
* Animates the small circles which is shown
* as capsule buttons.
* @param {number} opt_animationSpeed Animation speed
* @return {caq.raphael.LoggedIn}
*/
LoggedIn.prototype.animateSmallCircles = function(opt_animationSpeed) {
var animationSpeed = opt_animationSpeed || 1500,
length = this.smallCirclesSet.length,
smallRadius = this.smallRadius.initial,
self = this;
// We want to use this array, to choose
// the circle buttons in the correct order
// so that the further away circles can animate
// behind the shorter away ones.
var lineArray = [3, 0, 2, 1];
// we are looping through the smallCirclesSet
// and smallCircleTestsSet in a different way
// we defined in the lineArray.
for (var i = 0; i < length; i++) {
// this is where we do the trick.
var line = lineArray[i];
// this
(function(i){
setTimeout(function(){
self.smallCirclesSet[lineArray[i]].animate({
cx: self.smallCirclesSet[lineArray[i]].data('animate.x'),
r: smallRadius
}, animationSpeed, '<>');
self.smallCircleTextsSet[lineArray[i]].animate({
x: self.smallCircleTextsSet[lineArray[i]].data('animate.x'),
'font-size': smallRadius * 0.8,
opacity: 1
}, animationSpeed, '<>');
}, animationSpeed / (length * 1.5) * (i + 1));
}(i));
};
};
/**
* Animation of the strokes which
* appears around the main circle
* when we click to buttons.
* @param {caq.raphael.LoggedIn.Color} strokeColor
* @param {number} animationTime
* @return {void}
*/
LoggedIn.prototype.animateStrokes = function(strokeColor, animationTime) {
this.strokeSet.attr({stroke: strokeColor});
if(!this.animationDone) {
var topStroke = this.strokeSet[0],
bottomStroke = this.strokeSet[1];
topStroke.animate({
reverseArc: [this.center.x, this.center.y, 180, 360, this.bigRadius.initial]
}, animationTime, '>');
bottomStroke.animate({
arc: [this.center.x, this.center.y, 180, 360, this.bigRadius.initial]
}, animationTime, '>');
}
};
/**
* Animation of moving newly
* created strokes.
* @param {number} animationTime
* @return {void}
*/
LoggedIn.prototype.moveStrokes = function(animationTime) {
if(!this.animationDone) {
var topStroke = this.strokeSet[0],
bottomStroke = this.strokeSet[1];
topStroke.animate({
reverseArc: [this.center.x, this.padding.top, 180, 360, this.bigRadius.after]
}, animationTime);
bottomStroke.animate({
arc: [this.center.x, this.padding.bottom, 180, 360, this.bigRadius.after]
}, animationTime);
}
}
/**
* Moves the background canvases
* when clicked to a button.
* @return {void} [description]
*/
LoggedIn.prototype.moveCanvas = function() {
var self = this;
var topCanvas = self.backgroundCanvasSet[0],
bottomCanvas = self.backgroundCanvasSet[1];
topCanvas.animate({y: self.padding.top - topCanvas.attrs.height}, 500);
bottomCanvas.animate({y: self.padding.bottom}, 500);
};
/**
* Animation of the small circles
* This will be different for mobile
* and the desktop version of the
* website.
*
* @todo Mobile Movements.
*
* @return {void}
*/
LoggedIn.prototype.moveSmallCirclesUp = function() {
this.smallCirclesSet.animate({
cy: this.padding.top * 0.5,
r: this.smallRadius.after
}, 500);
this.smallCircleTextsSet.animate({
y: this.padding.top * 0.5,
'font-size': this.smallRadius.after * 0.8
}, 500);
};
/**
* Moves the main, big circle to
* the top along with its text.
* @return {void}
*/
LoggedIn.prototype.moveBigCircle = function() {
var topCircle = this.mainCircleSet[0],
bottomCircle = this.mainCircleSet[1],
mainText = this.mainText;
topCircle.animate({
path: this.getTransformPath(this.center.x, this.padding.top,
this.bigRadius.after, 0, 180)
}, 500);
bottomCircle.animate({
path: this.getTransformPath(this.center.x, this.padding.bottom,
this.bigRadius.after, 180, 360)
}, 500);
mainText.animate({
y: this.padding.top - (this.bigRadius.after * 0.4),
'font-size': this.bigRadius.after * 0.35
}, 450);
};
/**
* This is the animation of the Lines
* appear as a stroke of the background
* canvases when their animation is done.
* @param {caq.raphael.color} strokeColor
* @param {number} opt_animationTime
* @return {void}
*/
LoggedIn.prototype.animateLines = function(strokeColor, opt_animationTime) {
var animationTime = opt_animationTime || 500;
// animate and original properties are the properties
// of the line which will be animated in another method.
// we are setting them as datas, so that, another methods
// can use it not knowing how they have been created.
if(this.linesSet.length === 0) {
this.paper.setStart();
var line = this.paper.path(['M', this.center.x - this.bigRadius.after, this.padding.top])
.data('animate.x', 0).data('animate.y', this.padding.top)
.data('original.x', this.center.x - this.bigRadius.after)
.data('original.y', this.padding.top);
var line = this.paper.path(['M', this.center.x + this.bigRadius.after, this.padding.top])
.data('animate.x', this.width).data('animate.y', this.padding.top)
.data('original.x', this.center.x + this.bigRadius.after)
.data('original.y', this.padding.top);
var line = this.paper.path(['M', this.center.x - this.bigRadius.after, this.padding.bottom])
.data('animate.x', 0).data('animate.y', this.padding.bottom)
.data('original.x', this.center.x - this.bigRadius.after)
.data('original.y', this.padding.bottom);
var line = this.paper.path(['M', this.center.x + this.bigRadius.after, this.padding.bottom])
.data('animate.x', this.width).data('animate.y', this.padding.bottom)
.data('original.x', this.center.x + this.bigRadius.after)
.data('original.y', this.padding.bottom);
this.linesSet = this.paper.setFinish();
}
this.linesSet.attr({stroke: strokeColor, 'stroke-width': this.bigRadius.after / 12});
var ll = this.linesSet.length;
for(var i = 0; i < ll; i++) {
this.linesSet[i].animate({ path: [
'M', this.linesSet[i].data('original.x'), this.linesSet[i].data('original.y'),
'L', this.linesSet[i].data('animate.x'), this.linesSet[i].data('animate.y')
]}, animationTime);
}
};
/**
* Utility function which turns milliseconds
* into days, hours, minutes and seconds
* and puts them into an array.
* @param {number} s milliseconds
* @return {Array} Array of days, hours, mins, secs
*/
LoggedIn.prototype.msToTime = function(s) {
var ms = s % 1000;
s = (s - ms) / 1000;
var secs = s % 60;
s = (s - secs) / 60;
var mins = s % 60;
s = (s - mins) / 60;
var hrs = s % 24;
s = (s - hrs) / 24;
var days = s;
return [days, hrs, mins, secs];
};
/**
* Initialize the countdown timers.
* @param {number} capsuleId id of the capsule which is shown.
* @param {caq.raphael.color} color
* @return {void}
*/
LoggedIn.prototype.initTimers = function(capsuleId, color) {
if(typeof capsuleId !== 'number') {
capsuleId = parseInt(capsuleId);
}
var strings = ['DAYS', 'HOURS', 'MINUTES', 'SECONDS'];
// we are using this array as reference
// how they will be placed regarding to the origin.
// and then we are taking them back so that they can
// work as if they are [-1.5, -0.5, 0.5, 1.5]
// I could start for loop from -1 to the 2,
// but this is much more readable.
coordinates = [-1, 0, 1, 2];
// this if statement ensures that,
// necessary will be elements will be
// created only once and then that created
// instance will be used. Kind of singleton.
if(!this.timersCreated) {
for (var i = 0, ll = strings.length; i < ll; i++) {
// HOURS for strings
var xCor = this.center.x + (this.globalOffset * 3 * coordinates[i]) - (this.globalOffset * 1.5);
var tS = this.paper.text(xCor, this.center.y + this.bigRadius.initial * 0.2, strings[i])
.attr({
'stroke-width': 0,
'font-size': this.bigRadius.initial / 8,
'font-family': this.fontFamily
});
var tTS = this.paper.text(xCor, this.center.y - this.bigRadius.initial / 4, 0)
.attr({
'stroke-width': 0,
'font-size': this.bigRadius.initial / 1.5,
'font-family': this.fontFamily
});
this.timersGroupSet.push(tS, tTS);
this.timersSet.push(tTS);
} // endfor;
} //endif;
this.timersGroupSet.attr({fill: color});
this.timersSet.attr({text: 0});
var intervalStrings = [
LoggedIn.capsuleNames.MONTHLY,
LoggedIn.capsuleNames.QUARTERLY,
LoggedIn.capsuleNames.YEARLY,
LoggedIn.capsuleNames.DECADE
];
var self = this;
$.ajax({
url: "http://api.pastself.co/v0/countdown",
data: { time: intervalStrings[capsuleId - 1].toLowerCase()},
cache: false
})
.done(function( msg ) {
self.countdown = new Date(msg.response.countdowns[0]);
self.firstAjaxReceived = true;
});
// once everthing is done, we are
// setting the global boolean to true.
this.timersCreated = true;
};
/**
* Initialization of the '+' button.
* @param {number} opt_animationSpeed
* @return {void}
*/
LoggedIn.prototype.initAddPostButton = function(opt_animationSpeed) {
var animationSpeed = opt_animationSpeed || 500;
this.addPostButton.attr({
x: this.center.x,
y: this.padding.bottom + (this.bigRadius.after * 0.30),
'font-size': this.bigRadius.after,
'font-family': this.fontFamily,
fill: LoggedIn.colors.DARK,
cursor: 'pointer',
opacity: 0
})
.animate({opacity: 1}, animationSpeed);
};
/**
* Animation of the motivation text.
* @param {nubmer} opt_animationSpeed
* @return {void}
*/
LoggedIn.prototype.animateAddPostMotivationText = function(opt_animationSpeed) {
var x = this.center.x,
y = this.padding.bottom - (this.smallRadius.initial * 0.5),
t = 'Have your place in today\'s history.\nStart telling your story...';
if(!this.addPostMotivationText) {
this.addPostMotivationText = this.paper.text(x, y, t);
}
this.addPostMotivationText.attr({
x: x,
y: y,
text: t,
'font-size': this.smallRadius.after * 0.75,
'font-family': this.fontFamily,
fill: LoggedIn.colors.DARK,
cursor: 'pointer',
opacity: 0
});
var animationSpeed = opt_animationSpeed || 500;
this.addPostMotivationText.animate({opacity: 1}, animationSpeed);
return this;
};
/**
* Click event of the buttons.
* This is where almost all of the jobs happened.
* Maybe sign of a poor design, but we seperated
* all of the jobs to the necessary functions.
*
* @param {@event} evt
* @return {this} caq.raphael.LoggedIn
*/
LoggedIn.prototype.smallCircleClickEvent = function(evt) {
var self = this;
var target;
if(evt.target.nodeName === 'tspan') {
target = evt.target.parentNode;
}
else if(evt.target.nodeName === 'circle') {
target = evt.target;
}
var capsuleId = target.getAttribute('data-capsule-id');
strokeColor = target.getAttribute('data-stroke-color');
// if animation is done
// we don't need to animate those again, so we will make
// the animation done to true after all of the animations
// are done. So, it starts with the value of 500,
// and when animation is done, animation process
// will be 0 seconds.
var waitTime = this.animationDone ? 0 : 500;
// first phase of the second phase of the animation.
// animating the circular strokes around the
// main big circle.
this.animateStrokes(strokeColor, waitTime);
// all of animations are in 3 phases actually.
// first phase is when page is opened.
// last 2 phases are being occured when user
// clicks to the any of the buttons.
// Those need to be in order, so that i am using
// the setTimeout function to be sure those
// are occuring sequentially.
setTimeout(function() {
self.moveCanvas();
self.moveSmallCirclesUp();
self.moveBigCircle();
self.moveStrokes(waitTime);
setTimeout(function() {
self.animateLines(strokeColor);
self.initTimers(capsuleId, strokeColor);
self.initAddPostButton();
self.animateAddPostMotivationText();
}, waitTime);
self.animationDone = true;
}, waitTime);
return this;
};
/**
* Registering necessary events for
* necessary elements
* @return {void}
*/
LoggedIn.prototype.registerEvents = function() {
var self = this;
this.smallCirclesSet.click(function(evt) {
self.smallCircleClickEvent(evt);
})
.mouseover(function() {
this.animate({transform: 's1.1 1.1 ' + this.attrs.cx + ' ' + this.attrs.cy}, 200, '>');
})
.mouseout(function() {
this.animate({transform: ''}, 200, '>');
});
this.smallCircleTextsSet.click(function(evt) {
self.smallCircleClickEvent(evt);
});
this.createGroupSet.click(function(evt) {
console.log('addPostButton clicked');
})
};
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment