網頁「新手提示」懶人工具 1.1 版
1 |
前幾天分享了我的私房「網頁新手提示」懶人工具,得到不少回饋,其中有幾則是這麼說的:
這兩天,我越想越不對勁... 原本好意提醒使用者有哪些功能可用,但如果使用者不是新手,只是換電腦或清掉瀏覽器記錄,也得全程看完才能開始操作,心裡一定很賭爛吧?
我無法定義何謂「好的操作介面」,但剝奪使用者自主權的一定是爛介面(例如:為什麼關不掉?幹! 喵的,為什麼非要按完所有步驟才能存檔?),能隨時依使用者自由意志決定運作方法(除非是為了防呆或安全),是好軟體的要件之一。
為此,我做了一點小修改再推出 Ver 1.1 版,提示文字下方會增加一排導覽鈕,支援回看上一則提示(這點也很重要:使用者不小心按到了問你怎麼回上一則,總不能告訴他「人生的很多事物稍縱即逝。施主,放下吧」),跳下一則提示,或直接結束導覽,按鈕介面很陽春,但意思到了:
附上 Ver 1.1 程式碼,主要增加上一則下一則切換及導覽鈕:
var afet;
(function (afet) {
/**
* 簡易新手提示產生器 Ver 1.1.0 by Jeffrey https://blog.darkthread.net
* @param {any} guideCode 指示代碼,首次顯示後在localStorage下註記以免重複出現
* @param {boolean} force 無視 localStorage 已讀註記,一律顯示
* @param {any} instructions Key/Value 形式,Key為 Selector,Value 為說明物件
* @param {any} instrucStyles 說明區塊自訂樣式
* 說明物件格式為 { offsetTop: y, offsetLeft: x, text: '...', before: function(el) {...}, after: function(el) {...} }
* 說明物件也可以字串取代,則說明會寫示在選取元素下方,說明文字以可為 HTML 或純文字,若為純文字支援換行顯示
*/
function showNoviceGuide(guideCode, force, instructions, instrucStyles) {
var zIdx = 9990, bgColor = '#2196f3', key = "$NG_" + guideCode;
if (localStorage.getItem(key) && !force) return;
var docW = $(document).width() + 'px', docH = $(document).height() + 'px';
var mask = $('<div></div>').css({
position: 'absolute', top: 0, left: 0, width: docW, height: docH, 'z-index': zIdx,
opacity: 0.2, 'background-color': '#444', cursor: 'pointer'
});
mask.appendTo('body');
function getNumber(s) { return parseInt(s.replace('px', '')); }
var queue = Object.keys(instructions);
var curIdx = -1;
function isLastSel() { return curIdx >= queue.length - 1; }
function isFirstSel() { return curIdx <= 0; }
function getNextSel() {
if (isLastSel()) return null;
return queue[++curIdx];
}
function getPrevSel() {
if (isFirstSel()) return null;
return queue[--curIdx];
}
var lastTip, navBar;
var svg = $('<svg version="1.1"></svg>').css({ position: 'absolute', top: 0, left: 0, zIndex: zIdx + 1, width: docW, height: docH, opacity: 0.5, cursor: 'pointer' });
svg.appendTo('body');
//http://chubao4ever.github.io/tech/2015/07/16/jquerys-append-not-working-with-svg-element.html
function SVG(tag) {
return document.createElementNS('http://www.w3.org/2000/svg', tag);
}
function drawLine(x1, y1, x2, y2, color) {
$(SVG('line'))
.attr('x1', x1).attr('y1', y1).attr('x2', x2).attr('y2', y2)
.attr('stroke', color).attr('stroke-width', '2px')
.appendTo(svg);
}
function drawCircle(x, y, r, color) {
$(SVG('circle')).attr('cx', x).attr('cy', y).attr('r', r).attr('fill', color)
.appendTo(svg);
}
function goTip(prev) {
if (prev && isFirstSel()) return;
if (lastTip) {
lastTip.trigger('next').remove();
lastTip = null;
}
navBar && navBar.remove();
navBar = null;
svg.empty();
if (isLastSel() && !prev) {
mask.remove();
svg.remove();
window.scrollTo(0, 0);
localStorage.setItem(key, "Y");
return;
}
var sel = prev ? getPrevSel() : getNextSel();
var focusElem = $(sel);
var instruction = instructions[sel];
if (typeof instruction === "string") {
instruction = {
offsetTop: focusElem.height() + 20,
offsetLeft: 0,
text: instruction
};
}
if (focusElem.length && focusElem.is(":visible")) {
$.isFunction(instruction.before) && instruction.before(focusElem);
var pos = focusElem.offset();
var tip = $('<div></div>');
tip.css({
position: 'absolute',
top: (pos.top + instruction.offsetTop) + 'px',
left: (pos.left + instruction.offsetLeft) + 'px',
"z-index": zIdx + 2,
opacity: 1,
backgroundColor: bgColor,
padding: '6px',
color: 'white'
});
if (instrucStyles) tip.css(instrucStyles);
var instrText = instruction.text;
if (instrText.indexOf('<') === 0)
tip.html(instrText);
else {
tip.text(instrText);
if (instrText.indexOf('\n') > -1)
tip.html(tip.html().replace(/\n/g, '<br />'));
}
var s = getComputedStyle(focusElem[0]);
tip.attr('data-st', (pos.left + getNumber(s.paddingLeft) + focusElem.width() / 2) + ","
+ (pos.top + getNumber(s.paddingTop) + focusElem.height() / 2));
$.isFunction(instruction.after) && tip.bind("next", function() {
instruction.after(focusElem);
});
}
else {
//if not found, trigger click event to show next or prev tip
setTimeout(function () { goTip(prev); }, 0);
return;
}
lastTip = tip;
lastTip.appendTo('body');
var pos = lastTip.offset();
var st = lastTip.attr("data-st").split(',');
drawCircle(st[0], st[1], 5, bgColor);
drawLine(st[0], st[1], pos.left + lastTip.width() / 2, pos.top + lastTip.height() / 2, bgColor);
var y = st[1] - $(document).scrollTop()
console.log(y);
if (y > $(window).height() / 2) window.scrollTo(0, st[1]);
else if (y < 30) window.scrollTo(0, 0);
svg.hide().fadeIn('fast');
lastTip.hide().fadeIn('fast', function() {
//add navigation buttons
navBar = $('<div class="novice-g"></div>');
navBar.css({
position: 'absolute', top: pos.top + lastTip.height() + 20, left: pos.left,
zIndex: zIdx + 2, width: lastTip.width() + 12,
textAlign: 'right', minWidth: '8em'
});
if (!isFirstSel()) navBar.append('<button class=prev title="上一則提示">←</button>');
if (!isLastSel()) navBar.append('<button class=next title="下一則提示">→</button>');
navBar.append('<button class=close title="結束導覽">ⅹ</button>');
navBar.find('button')
.css({
padding: 0, width: '2em', height: '1.5em', lineHeight: '1.5em',
textAlign: 'center', marginLeft: '0.2em', opacity: 0.6,
color: 'white', backgroundColor: '#888',
border: 'none', textShadow: '1px 1px 1px #666'
})
.click(function() {
var b = $(this);
if (b.hasClass('prev')) goTip(true);
else if (b.hasClass('next')) goTip();
else if (b.hasClass('close')) {
curIdx = queue.length - 1;
goTip();
}
});
navBar.appendTo('body');
});
};
svg.click(function() { goTip(false); });
setTimeout(function () { svg.click(); }, 500);
}
afet.ShowNoviceGuide = showNoviceGuide;
})(afet || (afet = {}));
線上展示已同步更新。最後謝謝 Switch、柯達、Davide Lin 的回饋,讓我少掉一些被客訴的機會。
Add some improvement to novice guide library to provide previous / next navigation and close button to enhance user experience.
Comments
# by Huang
非常好用,特別是用在user的業務邏輯提示。非常謝謝