Created
January 29, 2017 23:22
-
-
Save humphreyja/eb8256ada8a78d6dcd1dfb1329d1f0e8 to your computer and use it in GitHub Desktop.
Info Message Box for Websites.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div id="vue-info"> | |
<div class="info-message info-message-1" :style="{top: html.top, left: html.left, right: html.right, bottom: html.bottom, position: html.position, height: html.height, width: html.width, maxHeight: html.maxHeight, maxWidth: html.maxWidth, display: containerOpen ? 'block' : 'none'}"> | |
<svg class="container" viewbox="0 0 800 700" :style="{top: 0, left: 0, height: svgHeight, width: svgWidth, display: mobile ? 'none' : 'block'}" :class="[svgOpen ? 'open' : '']"> | |
<defs> | |
<filter id="filter-dropshadow" x="0" y="0" width="140%" height="140%"> | |
<feOffset result="offOut" in="SourceAlpha" dx="6" dy="3" /> | |
<feColorMatrix result="matrixOut" in="offOut" type="matrix" | |
values="0 0 0 0 0 | |
0 0 0 0 0 | |
0 0 0 0 0 | |
0 0 0 0.7 0"/> | |
<feGaussianBlur result="blurOut" in="matrixOut" stdDeviation="3" /> | |
<feBlend in="SourceGraphic" in2="blurOut" mode="normal" /> | |
</filter> | |
</defs> | |
<path :d="[svgStart, svgTopLeftCurve, svgLeft, svgBottomLeftCurve, svgBottom, svgBottomRightCurve, svgRight, svgTopRightCurve, svgTop]" stroke-width="1" filter="url(#filter-dropshadow)" /> | |
</svg> | |
<div class="message-contents" :class="[svgOpen ? 'open' : '']" :style="{margin: messageMargin, padding: messagePadding, borderLeft: messageBorderLeft, borderTop: messageBorderTop, boxShadow: messageShadow}"> | |
{{{ messageHTML }}} | |
</div> | |
</div> | |
</div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Opens an info message by passing in a JQuery event object, a node, and an optional message to the `open` function. | |
// If no message if found, it will parse the node and any of its parents until if finds a 'data-info-message' attribute | |
// and will use the value for that attribute as its message. If it doesn't find that attribute, the message will not open. | |
// This is also mobile friendly and will change to a popup from the bottom if the window height is less than 800 or it will | |
// change to a popup from the right side if window width is less than 800. | |
(function() { | |
"use strict"; | |
App.register('component').enter(function() { | |
var infoMessage = new Vue({ | |
el: '#vue-info', | |
data: function() { | |
return { | |
messageHTML: "", | |
open1: false, | |
open2: false, | |
container: '#vue-info .info-message-1', | |
clickX: 0, | |
clickY: 0, | |
windowX: 0, | |
windowY: 0, | |
windowWidth: 0, | |
windowHeight: 0, | |
windowPadding: 10, | |
svgPadding: 6, | |
svgArrowPadding: 20, | |
svgHeight: 350, | |
svgWidth: 400, | |
svgCurve: 5, | |
svgRes: 2, | |
svgOpen: false, | |
containerOpen: false, | |
windowHeightBreakPoint: 800, | |
windowWidthBreakPoint: 800 | |
} | |
}, | |
ready: function() { | |
var self = this; | |
// Track window size changes | |
this.windowHeight = $(window).height(); | |
this.windowWidth = $(window).width(); | |
$(window).resize(function(){ | |
self.windowHeight = $(window).height(); | |
self.windowWidth = $(window).width(); | |
self.close(); | |
}); | |
// Detect clicks inside of container and stops the container from closing | |
$(this.container).click(function(e) { | |
var offset = $(this).offset(); | |
var posY = e.pageY - offset.top; | |
var posX = e.pageX - offset.left; | |
if (posY >= self.svg.container.top && posY <= self.svg.container.bottom && posX >= self.svg.container.left && posX <= self.svg.container.right){ | |
e.stopPropagation(); | |
return; | |
} | |
}); | |
}, | |
computed: { | |
mobile: function() { | |
// Checks if message should be displayed as mobile | |
if (this.direction === 'bottompanel' || this.direction === 'sidepanel') { | |
return true; | |
}else{ | |
return false; | |
} | |
}, | |
html: function() { | |
// HTML inline styles | |
var top = this.top + 'px', | |
bottom = 'auto', | |
right = 'auto', | |
left = this.left + 'px', | |
height = this.svgHeight + 'px', | |
width = this.svgWidth + 'px', | |
position = 'absolute', | |
maxHeight = 'auto', | |
maxWidth = 'auto'; | |
if (this.direction === 'sidepanel'){ | |
position = 'fixed'; | |
top = '0px'; | |
bottom = '0px'; | |
left = 'auto'; | |
right = '0px'; | |
height = 'auto'; | |
maxWidth = width; | |
width = '40%'; | |
}else if (this.direction === 'bottompanel') { | |
position = 'fixed'; | |
top = 'auto'; | |
bottom = '0px'; | |
left = '0px'; | |
right = '0px'; | |
width = 'auto'; | |
maxHeight = height; | |
height = '40%'; | |
} | |
return { | |
top: top, | |
bottom: bottom, | |
right: right, | |
left: left, | |
height: height, | |
width: width, | |
position: position | |
} | |
}, | |
svg: function() { | |
// The SVG specs | |
var arrowHeight = 14; | |
var top = this.svgPadding; | |
var bottom = this.svgHeight - (this.svgPadding); | |
var right = this.svgWidth - (this.svgPadding); | |
var left = this.svgPadding; | |
if (this.direction === 'down') { | |
top = arrowHeight * this.svgRes; | |
} | |
if (this.direction === 'up') { | |
bottom = this.svgHeight - (arrowHeight * this.svgRes); | |
} | |
return { | |
height: this.svgHeight * this.svgRes, | |
width: this.svgWidth * this.svgRes, | |
curve: this.svgCurve * this.svgRes, | |
padding: this.svgPadding * this.svgRes, | |
container: { | |
top: top, | |
bottom: bottom, | |
right: right, | |
left: left | |
}, | |
arrow: { | |
base: 14 * this.svgRes, | |
top: 4 * this.svgRes, | |
height: arrowHeight * this.svgRes, | |
point: 20 * this.svgRes, | |
mid: 30 * this.svgRes, | |
padding: this.svgArrowPadding * this.svgRes | |
} | |
} | |
}, | |
direction: function() { | |
// The direction the info message container shows up in respects to click position | |
if (this.windowWidth <= this.windowWidthBreakPoint) { | |
return 'bottompanel'; | |
} | |
if (this.windowHeight <= this.windowHeightBreakPoint) { | |
return 'sidepanel'; | |
} | |
if (this.windowY <= (this.windowHeight / 2)) { | |
return 'down'; | |
}else{ | |
return 'up'; | |
} | |
}, | |
top: function() { | |
// The Top position of the info svg container | |
if (this.direction === 'down'){ | |
return this.clickY + (this.svg.arrow.height / this.svgRes); | |
}else{ | |
return this.clickY - (this.svg.arrow.height / this.svgRes) - this.svgHeight; | |
} | |
}, | |
left: function() { | |
// The Left position of the info svg container | |
var left = this.clickX - (this.svgWidth / 2); | |
if (left < 0) { | |
return 0; | |
}else if ((left + this.svgWidth) >= this.documentWidth()){ | |
return this.documentWidth() - this.svgWidth; | |
}else{ | |
return left; | |
} | |
}, | |
svgTopArrowPadding: function() { | |
// The amount of space above the container for the arrow | |
if (this.direction === 'down') { | |
return this.svg.arrow.padding; | |
}else{ | |
return 0; | |
} | |
}, | |
svgBottomArrowPadding: function() { | |
// The amount of space below the container for the arrow | |
if (this.direction === 'up') { | |
return this.svg.arrow.padding; | |
}else{ | |
return 0; | |
} | |
}, | |
svgStart: function() { | |
// SVG Drawing: Move to start position | |
var x = this.svg.curve + this.svg.padding; | |
var y = 0 + this.svg.padding + this.svgTopArrowPadding; | |
return "M" + x + ' ' + y; | |
}, | |
svgTopLeftCurve: function(){ | |
// SVG Drawing: Draw Quadratic curve for top left corner | |
var cx = 0 + this.svg.padding; | |
var cy = 0 + this.svg.padding + this.svgTopArrowPadding; | |
var x = 0 + this.svg.padding; | |
var y = 0 + this.svg.curve + this.svg.padding + this.svgTopArrowPadding; | |
return "Q" + cx + ' ' + cy + ' ' + x + ' ' + y; | |
}, | |
svgBottomLeftCurve: function(){ | |
// SVG Drawing: Draw Quadratic curve for bottom left corner | |
var cx = 0 + this.svg.padding; | |
var cy = this.svg.height - this.svg.padding - this.svgBottomArrowPadding; | |
var x = 0 + this.svg.curve + this.svg.padding; | |
var y = this.svg.height - this.svg.padding - this.svgBottomArrowPadding; | |
return "Q" + cx + ' ' + cy + ' ' + x + ' ' + y; | |
}, | |
svgBottomRightCurve: function(){ | |
// SVG Drawing: Draw Quadratic curve for bottom right corner | |
var cx = this.svg.width - this.svg.padding; | |
var cy = this.svg.height - this.svg.padding - this.svgBottomArrowPadding; | |
var x = this.svg.width - this.svg.padding; | |
var y = this.svg.height - this.svg.curve - this.svg.padding - this.svgBottomArrowPadding; | |
return "Q" + cx + ' ' + cy + ' ' + x + ' ' + y; | |
}, | |
svgTopRightCurve: function(){ | |
// SVG Drawing: Draw Quadratic curve for top right corner | |
var cx = this.svg.width - this.svg.padding; | |
var cy = 0 + this.svg.padding + this.svgTopArrowPadding; | |
var x = this.svg.width - this.svg.curve - this.svg.padding; | |
var y = 0 + this.svg.padding + this.svgTopArrowPadding; | |
return "Q" + cx + ' ' + cy + ' ' + x + ' ' + y; | |
}, | |
svgLeft: function() { | |
// SVG Drawing: Draw Line on left side | |
var sx = 0 + this.svg.padding; | |
var sy = this.svg.height - this.svg.curve - this.svg.padding - this.svgBottomArrowPadding; | |
return 'L' + sx + ' ' + sy; | |
}, | |
svgBottom: function() { | |
// SVG Drawing: Draw Line on bottom side, if the container is 'up' in terms of the click position | |
// insert an arrow with respects to the position of the click | |
if (this.direction === 'up') { | |
var midPoint = (this.clickX - this.left) * this.svgRes; | |
var sx = midPoint - this.svg.arrow.mid; | |
var sy = this.svg.height - this.svg.padding - this.svgBottomArrowPadding; | |
var mid = sx + this.svg.arrow.mid; | |
var a1cx = mid - this.svg.arrow.base; | |
var a1cy = sy; | |
var a1x = mid - this.svg.arrow.top; | |
var a1y = sy + this.svg.arrow.height; | |
var a2cx = mid; | |
var a2cy = this.svg.height - this.svg.padding - this.svgBottomArrowPadding + this.svg.arrow.point; | |
var a2x = mid + this.svg.arrow.top; | |
var a2y = sy + this.svg.arrow.height; | |
var a3x = mid + this.svg.arrow.mid; | |
var a3y = sy; | |
var a3cx = mid + this.svg.arrow.base; | |
var a3cy = sy; | |
var ex = this.svg.width - this.svg.curve - this.svg.padding; | |
var ey = this.svg.height - this.svg.padding - this.svgBottomArrowPadding; | |
return 'L' + sx + ' ' + sy + ' Q' + a1cx + ' ' + a1cy + ' ' + a1x + ' ' + a1y + ' Q' + a2cx + ' ' + a2cy + ' ' + a2x + ' ' + a2y + ' Q' + a3cx + ' ' + a3cy + ' ' + a3x + ' ' + a3y + ' L' + ex + ' ' + ey; | |
}else{ | |
var sx = this.svg.width - this.svg.curve - this.svg.padding; | |
var sy = this.svg.height - this.svg.padding - this.svgBottomArrowPadding; | |
return 'L' + sx + ' ' + sy; | |
} | |
}, | |
svgRight: function() { | |
// SVG Drawing: Draw Line on right side | |
var sx = this.svg.width - this.svg.padding; | |
var sy = 0 + this.svg.curve + this.svg.padding + this.svgTopArrowPadding; | |
return 'L' + sx + ' ' + sy; | |
}, | |
svgTop: function() { | |
// SVG Drawing: Draw Line on top side, if the container is 'down' in terms of the click position | |
// insert an arrow with respects to the position of the click | |
if (this.direction === 'down') { | |
var midPoint = (this.clickX - this.left) * this.svgRes; | |
var sx = midPoint + this.svg.arrow.mid; | |
var sy = 0 + this.svg.padding + this.svgTopArrowPadding; | |
var mid = sx - this.svg.arrow.mid; | |
var a1cx = mid + this.svg.arrow.base; | |
var a1cy = sy; | |
var a1x = mid + this.svg.arrow.top; | |
var a1y = sy - this.svg.arrow.height; | |
var a2cx = mid; | |
var a2cy = this.svgTopArrowPadding - this.svg.arrow.point + this.svg.padding; | |
var a2x = mid - this.svg.arrow.top; | |
var a2y = sy - this.svg.arrow.height; | |
var a3x = mid - this.svg.arrow.mid; | |
var a3y = sy; | |
var a3cx = mid - this.svg.arrow.base; | |
var a3cy = sy; | |
var ex = this.svg.curve + this.svg.padding; | |
var ey = 0 + this.svg.padding + this.svgTopArrowPadding; | |
return 'L' + sx + ' ' + sy + ' Q' + a1cx + ' ' + a1cy + ' ' + a1x + ' ' + a1y + ' Q' + a2cx + ' ' + a2cy + ' ' + a2x + ' ' + a2y + ' Q' + a3cx + ' ' + a3cy + ' ' + a3x + ' ' + a3y + ' L' + ex + ' ' + ey; | |
}else{ | |
var sx = this.svg.curve + this.svg.padding + this.svgTopArrowPadding; | |
var sy = 0 + this.svg.padding + this.svgTopArrowPadding; | |
return 'L' + sx + ' ' + sy; | |
} | |
}, | |
messageMargin: function(){ | |
// Margin for the contents of the container. If NOT mobile, calculate the margin based on SVG | |
if (this.mobile) { | |
return 0; | |
} | |
var top = this.svg.padding + (this.svgTopArrowPadding / this.svgRes); | |
var bottom = this.svg.padding + (this.svgBottomArrowPadding / this.svgRes); | |
var right = this.svg.padding; | |
var left = this.svg.padding; | |
return top + 'px ' + left + 'px ' + bottom + 'px ' + right + 'px'; | |
}, | |
messagePadding: function(){ | |
// Padding for the contents of the container. If mobile, use the padding for the svg so its consistent | |
if (this.mobile) { | |
return this.svg.padding + 'px'; | |
}else{ | |
return 0; | |
} | |
}, | |
messageBorderLeft: function(){ | |
// Left Border for the container on mobile | |
if (this.direction === 'sidepanel') { | |
return '1px solid'; | |
}else{ | |
return 0; | |
} | |
}, | |
messageBorderTop: function(){ | |
// Top Border for the container on mobile | |
if (this.direction === 'bottompanel') { | |
return '1px solid'; | |
}else{ | |
return 0; | |
} | |
}, | |
messageShadow: function(){ | |
// box shadow for the container on mobile | |
if (this.mobile) { | |
return '0 0 6px #000'; | |
}else{ | |
return 'none'; | |
} | |
} | |
}, | |
methods: { | |
documentHeight: function(){ | |
// Fetch document height | |
return $(document).height(); | |
}, | |
documentWidth: function(){ | |
// Fetch document width | |
return $(document).width(); | |
}, | |
findMessage: function(node) { | |
// recursively fetches a message from the first 'data-info-message' attribute on any parent node of the clicked node | |
if ($(node).data('info-message') === undefined) { | |
if ($(node).parent().is('html')) { | |
return false; | |
}else{ | |
return this.findMessage($(node).parent()); | |
} | |
}else{ | |
return $(node).data('info-message'); | |
} | |
}, | |
open: function(args) { | |
// Opens the info message if the event, node, and a message is found | |
if (args.event === undefined || args.node === undefined) { | |
return; | |
} | |
var $event = args.event; | |
var node = args.node; | |
var msg = args.message || this.findMessage(node); | |
if (msg == false) { | |
return; | |
}else{ | |
this.messageHTML = msg; | |
this.clickX = $event.pageX; | |
this.clickY = $event.pageY; | |
this.windowX = $event.clientX; | |
this.windowY = $event.clientY; | |
this.containerOpen = true; | |
var self = this; | |
// Opens the container, makes sure events are in order | |
setTimeout(function () { | |
self.svgOpen = true; | |
// On the first click of the window, close the message (unless clicked in the container) | |
setTimeout(function () { | |
$(window).one('click', function() { | |
self.close(); | |
}); | |
}, 10); | |
}, 10); | |
} | |
}, | |
close: function() { | |
// Closes the message | |
this.svgOpen = false; | |
var node = $(this.container); | |
var self = this; | |
// hides the container after fade out | |
setTimeout(function () { | |
self.containerOpen = false; | |
}, 200); | |
} | |
} | |
}); | |
$("section").on('dblclick', function(event) { | |
infoMessage.open({event: event, node: this}); | |
}); | |
}); | |
})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$background-color: #eeeeee; | |
$border-color: #aaaaaa; | |
#vue-info { | |
svg.container { | |
@include transform(translateY(-20px) scale(0.8)); | |
@include transition(all, 0.2s); | |
opacity: 0; | |
position: absolute; | |
z-index: -1; | |
path { | |
fill: $background-color; | |
stroke: $border-color; | |
} | |
&.open { | |
@include transform(none); | |
opacity: 1; | |
@include transition(all, 0.2s); | |
} | |
} | |
.info-message { | |
position: absolute; | |
z-index: 100000000; | |
.message-contents { | |
overflow: scroll; | |
top: 0; | |
bottom: 0; | |
right: 0; | |
left: 0; | |
position: absolute; | |
opacity: 0; | |
background: $background-color; | |
border-color: $border-color !important; | |
&.open { | |
@include transform(none); | |
opacity: 1; | |
@include transition(all, 0.4s); | |
@include transition-delay(0.2s); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Preview of what it looks like by default