(function($){
var defaults = {
bgPos : '30px'
},
canvas = document.createElement('canvas'), // Canvas used to check text widths.
context = canvas.getContext('2d'),
iCaretPos, caret, totalWidth, cssRules, font, totalTextWidth, currentTextWidth,
locationPercentage, textWidthRatio, settings, selectors = [],
// find in which direction the user has selected the text, left or right
getSelectionDirection = {
direction : null,
lastOffset : null,
set : function(e){
if( e.shiftKey && e.keyCode == 36 )
getSelectionDirection.direction = 'left';
else if( e.shiftKey && e.keyCode == 35 )
getSelectionDirection.direction = 'right';
if( e.type == 'mousedown' )
getSelectionDirection.lastOffset = e.clientX;
else if( e.type == 'mouseup' ){
getSelectionDirection.direction = e.clientX < getSelectionDirection.lastOffset ? 'left' : 'right';
//console.log(direction);
}
}
};
function getCaretPosition(oField, direction){
// if IE
if( document.selection ){
oField.focus();
var oSel = document.selection.createRange();
oSel.moveStart('character', -oField.value.length);
iCaretPos = oSel.text.length;
}
else if( oField.selectionStart || oField.selectionStart == '0' )
iCaretPos = direction == 'left' ? oField.selectionStart : oField.selectionEnd;
return iCaretPos || 0;
}
// because CUT and PASTE events are fired in jQuery before the value has changed
function cutPasteDelay(e){
var that = this;
setTimeout(function(){
initIndicator.apply(that, e);
},100);
}
function initIndicator(e){
caret = getCaretPosition(this, getSelectionDirection.direction);
totalWidth = $(this).width(); // width of the input field
cssRules = window.getComputedStyle(this);
font = {
size : parseInt(cssRules.getPropertyValue("font-size")),
family : cssRules.getPropertyValue('font-family')
};
totalTextWidth = checkTextWidth(this.value, font);
currentTextWidth = checkTextWidth(this.value.substring(0,caret), font); // from the beginning index until current caret position
locationPercentage = Math.floor((currentTextWidth / totalTextWidth) * 100);
textWidthRatio = (totalWidth / totalTextWidth);
setStyles(this);
}
// update the visual style of the element
function setStyles(el){
// check the input's value overflows
if( textWidthRatio > 1 )
textWidthRatio = 0;
$(el).css({
'background-size' : textWidthRatio*100 + '%',
'background-position' : locationPercentage + '% ' + settings.bgPos
});
}
// check a given text lengh, acording to it's size and font family
function checkTextWidth(text, font){
context.font = font.size + "px " + font.family;
if( context.fillText )
return context.measureText(text).width;
else if( context.mozDrawText )
return context.mozMeasureText(text);
}
function destroy(selector){
$(document)
.off('mousedown mouseup keydown', selector, getSelectionDirection.set)
.off('keydown keyup focus select mouseup cut paste', selector, initIndicator)
.off('cut paste', selector, cutPasteDelay);
// remove the selector from the list of selectors
var index = selectors.indexOf(selector);
selectors.splice(index, 1);
// remove the visual indicator
return $(selector).css('background-position', '');
}
$.fn.inputIndicator = function(options){
var pluginSelector = this.selector;
if( options == 'destroy' )
return destroy(pluginSelector);
// merge settings
settings = $.extend( {}, defaults, options );
// check if the same selector was delegated already, not to create duplicate events on the Document
if( selectors.indexOf(pluginSelector) != -1 )
return this;
selectors.push( pluginSelector );
// make sure to initialize any input element that is inserted to the DOM after the plugin has first ran.
$(document).bind('DOMNodeInserted', function(e){
if( $(e.target).is(pluginSelector) )
$(e.target).trigger('keydown');
else
$(e.target).find(pluginSelector).trigger('keydown');
});
// init the whole thing and bind the events
$(document)
.on('mousedown mouseup keydown', this.selector, getSelectionDirection.set)
.on('keydown keyup focus select mouseup cut paste', this.selector, initIndicator)
.on('cut paste', this.selector, cutPasteDelay);
this.trigger('mouseup'); // make the indicator show the first time (without any user interaction)
return this;
};
})(jQuery);
$('input:text').inputIndicator({bgPos:'31px'});
input{
padding:5px; margin: 20px; width:300px; font-size:20px;
border:1px solid #CCC;
background-image: linear-gradient(0deg, #1a60a6 100%, transparent 0%);
background-image: -webkit-linear-gradient(0deg, #1a60a6 100%, transparent 0%);
background-repeat: no-repeat;
background-position:0 100px;
transition:200ms;
-webkit-transition:200ms;
-o-transition:200ms;
}
/* for ugly chrome */
textarea:focus, input:focus{
outline: none;
}
/* for centering only */
input{ display:inline-block; vertical-align:middle; }
html, body{ height:100%; text-align: center; }
body:before{content:''; display:inline-block; height:100%; vertical-align:middle; margin-right:-0.25em; }
<input type='text' value='2012年現在、国際連合の統計によると、東京は世界最大のメガシティと評価されており、世界最大の人口を有する都市圏を形成している。都市単位の経済規模(GDP)ではニューヨークをしのぎ、世界最大である。' />