UNPKG

guides

Version:

Simple way to highlighting DOM elements and guide your users with step-by-step welcome tours in your web app.

229 lines (208 loc) 8.39 kB
var format = require('./format'); var Guide = function (guide, $container, options) { this.guide = guide; this._distance = guide.distance || options.distance; this._color = guide.color || options.color; this._class = guide.cssClass || options.cssClass || ''; this.$highlightedElement = $(guide.element).addClass('guides-current-element'); this.$container = $container; this.init(); }; Guide.prototype._arrowSize = 10; // Mx1,y1, Cdx1,dy1,dx2,dy2,x2,y2 // (x1,y1) - start point // (dx1,dy1) - curve control point 1 // (dx2,dy2) - curve control point 2 // (x2,y2) - end point Guide.prototype._path = 'M{0},{1} C{2},{3},{4},{5},{6},{7}'; Guide.prototype._arrowTemplate = '<svg width="{0}" height="{1}">\ <defs>\ <marker id="arrow" markerWidth="13" markerHeight="13" refX="2" refY="6" orient="auto">\ <path d="M2,1 L2,{3} L{3},6 L2,2" style="fill:{4};"></path>\ </marker>\ </defs>\ <path id="line" d="{2}" style="stroke:{4}; stroke-width: 1.25px; fill: none; marker-end: url(#arrow);"></path>\ </svg>'; Guide.prototype.init = function() { this.$guide = $('<div />', { 'class': 'guides-fade-in guides-guide ' + this._class, 'html': '<span>' + this.guide.html + '</span>' }); this._position(); return this; }; Guide.prototype._position = function () { if (this.$highlightedElement && this.$highlightedElement.length > 0) { this._attachToElement(); this.$guide.appendTo(this.$container); this._renderArrow(); } else { this._center(); } this._scrollIntoView(); }; Guide.prototype._center = function () { this.$guide .css('visibility', 'hidden') .appendTo(this.$container) .addClass('guides-center') .css({ left: 0, right: 0, textAlign: 'center', top: (window.innerHeight / 2) - (this.$guide.outerHeight() / 2) }).css('visibility', 'visible'); }; Guide.prototype._attachToElement = function () { var elOffset = this.$highlightedElement.offset(), docWidth = $('body').width(), docHeight = $('body').height(), leftSpace = elOffset.left, topSpace = elOffset.top, highlightedElementWidth = this.$highlightedElement.outerWidth(), highlightedElementHeight = this.$highlightedElement.outerHeight(), rightSpace = docWidth - leftSpace - highlightedElementWidth, bottomSpace = docHeight - topSpace - highlightedElementHeight, css = { color: this._color, top: docHeight / 2 > elOffset.top ? elOffset.top : 'auto', left: docWidth / 2 > elOffset.left ? elOffset.left : 'auto', right: docWidth / 2 > elOffset.left ? 'auto' : docWidth - elOffset.left - highlightedElementWidth, bottom: docHeight / 2 > elOffset.top ? 'auto' : elOffset.bottom }; switch (Math.max(leftSpace, rightSpace, topSpace, bottomSpace)) { case leftSpace: this.position = 'left'; css.paddingRight = this._distance; css.right = $(document).width() - elOffset.left; css.left = 'auto'; break; case topSpace: this.position = 'top'; css.paddingBottom = this._distance; css.bottom = $(document).height() - elOffset.top; css.top = 'auto'; break; case rightSpace: this.position = 'right'; css.paddingLeft = this._distance; css.left = elOffset.left + highlightedElementWidth; css.right = 'auto'; break; default: this.position = 'bottom'; css.paddingTop = this._distance; css.top = elOffset.top + highlightedElementHeight; css.bottom = 'auto'; break; } this.$guide.addClass('guides-' + this.position).css(css); }; Guide.prototype._renderArrow = function() { this._width = this.$guide.outerWidth(); this._height = this.$guide.outerHeight(); this.$guide.append(format(this._arrowTemplate, this._width, this._distance, this[this.position](), this._arrowSize, this._color)); }; Guide.prototype.top = function () { var coord = this._verticalAlign(); return this._getPath(coord); }; Guide.prototype.bottom = function () { var coord = this._verticalAlign(true); return this._getPath(coord); }; Guide.prototype.left = function () { var coord = this._horizontalAlign(); return this._getPath(coord); }; Guide.prototype.right = function () { var coord = this._horizontalAlign(true); return this._getPath(coord); }; Guide.prototype._getPath = function (coord) { return format(this._path, coord.x1, coord.y1, coord.dx1, coord.dy1, coord.dx2, coord.dy2, coord.x2, coord.y2); }; Guide.prototype._getFluctuation = function () { return Math.floor(Math.random() * 20) + 10; }; Guide.prototype._verticalAlign = function (bottom) { var x1 = this._width / 2, y1 = bottom ? this._distance : 0, x2 = Math.max( Math.min( this.$highlightedElement.offset().left + (this.$highlightedElement.outerWidth() / 2) - this.$guide.offset().left, this._width - this._arrowSize), this._arrowSize), y2 = bottom ? this._arrowSize : this._distance - this._arrowSize; return { x1: x1, y1: y1, x2: x2, y2: y2, dx1: Math.max(0, Math.min(Math.abs((2 * x1) - x2) / 3, this._width) + this._getFluctuation()), dy1: bottom ? Math.max(0, y2 + (Math.abs(y1 - y2) * 3 / 4)) : Math.max(0, y1 + (Math.abs(y1 - y2) * 3 / 4)), dx2: Math.max(0, Math.min(Math.abs(x1 - (x2 * 3)) / 2, this._width) - this._getFluctuation()), dy2: bottom ? Math.max(0, y2 + (Math.abs(y1 - y2) * 3 / 4)) : Math.max(0, y1 + (Math.abs(y1 - y2) * 3 / 4)) } }; Guide.prototype._horizontalAlign = function (right) { var x1 = right ? this._distance : this._width - this._distance, y1 = this._height / 2, x2 = right ? this._arrowSize : this._width - this._arrowSize, y2 = Math.max( Math.min( this.$highlightedElement.offset().top + (this.$highlightedElement.outerHeight() / 2) - this.$guide.offset().top, this._height - this._arrowSize), this._arrowSize); return { x1: x1, y1: y1, x2: x2, y2: y2, dx1: right ? Math.max(0, x2 + (Math.abs(x1 - x2) * 3 / 4)) : Math.max(0, x1 + Math.abs(x1 - x2) * 3 / 4), dy1: Math.max(0, Math.min(Math.abs((2 * y1) - y2) / 3, this._height) + this._getFluctuation()), dx2: right ? Math.max(0, x2 + (Math.abs(x1 - x2) * 3 / 4)) : Math.max(0, x1 + Math.abs(x1 - x2) * 3 / 4), dy2: Math.max(0, Math.min(Math.abs(y1 - (y2 * 3)) / 2, this._height) + this._getFluctuation()) } }; Guide.prototype._scrollIntoView = function () { if (this.$highlightedElement.length === 0) { $('html,body').animate({ scrollTop: 0 }, 500); return; } var guideOffset = this.$guide.offset(), top = guideOffset.top, bottom = guideOffset.top + this.$guide.outerHeight(), left = guideOffset.left, right = guideOffset.left + this.$guide.outerWidth(), scrollTop = $(document).scrollTop(), scrollLeft = $(document).scrollLeft(); //scroll vertically to element if it is not visible in the view port if (scrollTop > top || scrollTop + $(window).height() < bottom) { $('html,body').animate({ scrollTop: this.position === 'bottom' ? top - 100 : top }, 500); } //scroll horizontally to element if it is not visible in the view port if (scrollLeft > left || scrollLeft + $(window).width() < right) { $('html,body').animate({ scrollLeft: this.position === 'righ' ? left - 100 : left }, 500); } }; Guide.prototype.destroy = function() { this.$highlightedElement.removeClass('guides-current-element'); this.$guide.remove(); }; module.exports = Guide;