获取光标位置,在很多场景中都十分有用,但是这个东西又不是那么好弄,在学习的过程中,找到这个文章,貌似被墙了,所以转载过来,也做个记录。

在任何编辑器中,获取光标位置都是非常重要的,很多人可能认为较难,其实只要处理好浏览器的兼容,还是比较容易实现的。

下面我们一起来看看如何获取到 Textarea 元素中的光标位置(测试地址1(墙) 测试地址2)。

首先,我们用 rangeData 对象作为数据存储,并获得焦点:

var rangeData = {start: 0, end: 0, text: "" };
textarea.focus();

对于非 IE 浏览器获取选区的起始和末尾位置其实非常容易:

rangeData.start= el.selectionStart;
rangeData.end = el.selectionEnd;

通过截取我们可以得到光标的选区内容:

rangeData.text = (rangeData.start != rangeData.end) ? el.value.substring(rangeData.start, rangeData.end): "";

而对于 IE 浏览器处理起来就比较麻烦了,但我们依旧可以获取到选区:

oS = document.selection.createRange();

同时还可获取 Textarea 元素的选区:

// 为了使 oR 与 oS 在同一等级上比较,请勿使用:oR = textarea.createTextRange()
oR = document.body.createTextRange();
oR.moveToElementText(textarea);

如果光标在 Textarea 元素内,很自然 oS.text 就是我们需要的选区内容:

rangeData.text = oS.text;

并且我们可以通过 oS.getBookmark() 方法获取到选区的位置数据,该位置数据可以通过 moveToBookmark() 方法设置回去。

    getBookmark: Retrieves a bookmark (opaque string) that can be used with moveToBookmark to return to the same range.

    moveToBookmark: Moves to a bookmark.

我们用 rangeData.bookmark 来记录该位置数据:

rangeData.bookmark = oS.getBookmark();

下面是最重要的步骤:我们比较 oR 与 oS 的选区起始位置(使用 object.compareEndPoints(sType, oRange) 方法比较),如果 oR 的起始位置在 oS 之前,我们向前移动 oS 的起始位置1个字符(使用 object.moveStart(sUnit [, iCount]) 方法移动),一直当 oS 的起始位置在 oR 之前停止,移动的位置,则是选区的起始位置。

    compareEndPoints: Compares an end point of a TextRange object with an end point of another range.

    moveStart: Changes the start position of the range.
for (i = 0; oR.compareEndPoints('StartToStart', oS) < 0 && oS.moveStart("character", -1) !== 0; i ++) {}
rangeData.start = i;

但由于在 IE 中,Textarea 元素中的所有换行符都占 1 个字符,可以通过 alert(textarea.value.length) 查看,故要对上面的代码做部分处理:

for (i = 0; oR.compareEndPoints('StartToStart', oS) < 0 && oS.moveStart("character", -1) !== 0; i ++) {
    // Why? You can alert(textarea.value.length)
    if (textarea.value.charAt(i) == '\n') {
        i ++;
    }
}
rangeData.start = i;

既然得到了选区的起始位置和选区字符串的字符,很自然我们可以计算得到选区的末尾位置:

rangeData.end = rangeData.text.length + rangeData.start;

获取 Textarea 的光标位置的 getCursorPosition 函数方法整理如下:

/**
* getCursorPosition Method
*
* Created by Blank Zheng on 2010/11/12.
* Copyright (c) 2010 PlanABC.net. All rights reserved.
*
* The copyrights embodied in the content of this file are licensed under the BSD (revised) open source license.
*/
function getCursorPosition(textarea) {
    var rangeData = {text: "", start: 0, end: 0 };
        textarea.focus();
    if (textarea.setSelectionRange) { // W3C
        rangeData.start= textarea.selectionStart;
        rangeData.end = textarea.selectionEnd;
        rangeData.text = (rangeData.start != rangeData.end) ? textarea.value.substring(rangeData.start, rangeData.end): "";
    } else if (document.selection) { // IE
        var i,
            oS = document.selection.createRange(),
            // Don't: oR = textarea.createTextRange()
            oR = document.body.createTextRange();
        oR.moveToElementText(textarea);

        rangeData.text = oS.text;
        rangeData.bookmark = oS.getBookmark();

        // object.moveStart(sUnit [, iCount])
        // Return Value: Integer that returns the number of units moved.
        for (i = 0; oR.compareEndPoints('StartToStart', oS) < 0 && oS.moveStart("character", -1) !== 0; i ++) {
            // Why? You can alert(textarea.value.length)
            if (textarea.value.charAt(i) == '\n') {
                i ++;
            }
        }
        rangeData.start = i;
        rangeData.end = rangeData.text.length + rangeData.start;
    }

    return rangeData;
}

得到 Textarea 元素光标位置,当Textarea 中的光标丢失了,再设置回来就简单多了:

/**
* setCursorPosition Method
*
* Created by Blank Zheng on 2010/11/12.
* Copyright (c) 2010 PlanABC.net. All rights reserved.
*
* The copyrights embodied in the content of this file are licensed under the BSD (revised) open source license.
*/
function setCursorPosition(textarea, rangeData) {
    if(!rangeData) {
        alert("You must get cursor position first.")
    }
    if (textarea.setSelectionRange) { // W3C
        textarea.focus();
        textarea.setSelectionRange(rangeData.start, rangeData.end);
    } else if (textarea.createTextRange) { // IE
        var oR = textarea.createTextRange();
        // Fixbug :
        // In IE, if cursor position at the end of textarea, the setCursorPosition function don't work
        if(textarea.value.length === rangeData.start) {
            oR.collapse(false)
            oR.select();
        } else {
            oR.moveToBookmark(rangeData.bookmark);
            oR.select();
        }
    }
}

测试地址1 (墙) 测试地址2

扩展阅读:
《TextRange Prototype》
《TextArea Cursor Position with JavaScript》

文章转载自:PlanABC – 怿飞’s Blog

继承链(原型链)以及构造函数

JavaScript 对象都有一个 prototype 属性,这个属性的作用就是为了方便的进行 继承。一个对象的原型属性,可以被设置为另外一个对象创建的继承链的实例,也就是说,对象 B在创建的时候,可以继承对象 A 的 prototype 属性。

function Shape(name) {
  this.x = 0;
  this.y = 0;
  this.name = name;
  console.log('Shape constructor called');
}

Shape.prototype = {
  move: function(x, y) {
    this.x += x;
    this.y += y;
  },

  toString: function() {
    return 'name: ' + this.name + ', at x: ' + this.x + ', y:' + this.y;
  }
};

// Rectangle
function Rectangle(name) {
  this.name = name;
  console.log('Rectangle constructor called');
}

Rectangle.prototype = new Shape();

var rect = new Rectangle('Player 1');
rect.move(1, 1);
console.log(rect.toString());
console.log(rect instanceof Rectangle);

此代码执行后会得到:

Shape constructor called
Rectangle constructor called
name: Player 1, at x: 1, y:1
true

结果表明,Shape 和 Rectangle 这两个构造函数( constructor )都被调用了。这是因为这句 Rectangle.prototype = new Shape();–new Rectangle() 并不会使其父对象的构造函数自动被调用,这也就是为什么两个构造函数都有 this.name = name; 了。

这里的 Shape.prototype 的方法 rect.move 和 rect.toString,当代码执行到这里的时候,如果构造函数 Rectangle 中有此方法,则就执行此方法,如果构造函数 Rectangle 中 没有此方法,则会向上寻找到 Rectangle.prototype ,如果找到,则执行,否则则返回 undefined 。如果构造函数 Rectangle 中和 Rectangle.prototype 都有此方法的话,则优先使用构造函数 Rectangle 中的方法,而不是 Rectangle.prototype 的。

调用父方法

但是,如果想要 Rectangle 使用一个不同的 move 方法,而又想重用原始的 Shape 方法的话,这时最好的方法就是使用 Function.prototype.apply:

Rectangle.prototype.move = function(x, y) {
  console.log('Super method called');
  Shape.prototype.move.apply(this, arguments);
};

虽然 Shape.prototype.move.apply 看起来很复杂, 但是如果把它拆解开来看的话,其实也比较简单。

1. 想要使用 Shape 调用 move 方法。
2. 此方法被存储在 Shape.prototype.move 中。
3. 由于是一个函数,所以有很多方法可以调用。
4. apply 方法可以调用一个函数而不用创建一个新实例。
5. 可以使用一些参数。

当函数执行的时候,arguments 对象,会被解释器所创建,其实就是传入的参数列表,类似参数的数组。而 this 则又是两一个深奥的东西了,那么下次再说。

FileReader 对象被用来,通过浏览器来读取文件数据,在上篇文章中,讨论了可以使用 FileReader 用多种格式来方便的读取文件数据,在很多方面,FileReader 和 XMLHttpRequest 非常类似。

Progress events (进度事件)

实际上,Progress 事件是一个单独定义的很普通的规范,这些事件被定义来大概的显示数据传输的进度,例如,当从服务器传送数据开始发生的时候,同样的可以是FileReader 对象开始从磁盘读取数据事件发生的时候。

总共有六个事件:

loadstart – 当加载数据开始的时候,此事件通常都是使用在整个过程的最开始。
progress – 当数据读取的时候,可以获得数据读取进度。
error – 当数据加载失败的时候。
abort – 当数据加载被 abort() 方法取消的时候,(在 FileReader 和 XMLHttpRequest 都可用)。
load – 当所有数据都被成功加载的时候。
loadend – 当数据完成传输的时候,一般情况下,都是在出错,取消,或者成功加载后。所以其不区分成功与否。只要过程完成就好。

error 和 load 已经在上篇文章中讨论过了,这里将讨论其他的一些事件。

跟踪进度

当想要知道文件的读取进度的时候,就应该使用 progress 事件,此事件对象,包含了三个属性,来表示文件开始传输之后的状态。

lengthComputable ─ true或者false,表示请求数据的大小是否已知。
loaded ─ 目前接收到的字节数。
total ─ 整个请求中期望传输的字节数。
注意:当lengthComputable为false时,total属性值为0。loadstart事件只会被激发一次。在loadstart之后,进度事件可能被激发0次或多次。

这些数据的作用是可以使 进度条(progress Html5)使用 progress 事件的信息进行进度展示。例如使用 Html5 的 progress 标签来展示数据读取进度。

var reader = new FileReader(),
     progressNode = document.getElementById("my-progress");

reader.onprogress = function(event) {
    if (event.lengthComputable) {
        progressNode.max = event.total;
        progressNode.value = event.loaded;
    }
};

reader.onloadend = function(event) {
    var contents = event.target.result,
        error    = event.target.error;

    if (error != null) {
        console.error("File could not be read! Code " + error.code);
    } else {
        progressNode.max = 1;
        progressNode.value = 1;
        console.log("Contents: " + contents);
    }
};

reader.readAsText(file);

相同的例子就是 Gmail 使用的拖拽上传,当把文件拖拽到 Gmail 里之后,就会立即出现进度条,显示上传进度。

处理错误

虽然是从本地上传文件,但有时候还是会失败。File API 定义了四种错误类型。

NotFoundError – 找不到文件。
SecurityError – 文件本身或者读取存在危险,一般情况下,如果读取此文件是危险的或者已经读取了太多次此文件,就会出现该错误。
NotReadableError – 文件存在,但是没有权限读取。
EncodingError – 编码错误,只要是作为 data URI 读取的时候,读取结果的长度超过了浏览器支持的最大长度。

当读取文件时有错误发生时,FileReader 对象的错误属性,就会成为上面提到的一个错误类型的一个实例。在规范中,浏览器都会用一个代码表明错误的属性和值。

FileError.NOT_FOUND_ERR -- NotFoundError.
FileError.SECURITY_ERR -- SecurityError.
FileError.NOT_READABLE_ERR -- NotReadableError.
FileError.ENCODING_ERR for -- EncodingError .
FileError.ABORT_ERR -- 当 abort() 方法调用的时候。

可以通过下面的代码测试不同的错误:

var reader = new FileReader();

reader.onloadend = function(event) {
    var contents = event.target.result,
        error    = event.target.error;

    if (error != null) {
        switch (error.code) {
            case error.ENCODING_ERR:
                console.error("Encoding error!");
                break;

            case error.NOT_FOUND_ERR:
                console.error("File not found!");
                break;

            case error.NOT_READABLE_ERR:
                console.error("File could not be read!");
                break;

            case error.SECURITY_ERR:
                console.error("Security issue with file!");
                break;

            default:
                console.error("I have no idea what's wrong!");
        }
    } else {
        progressNode.max = 1;
        progressNode.value = 1;
        console.log("Contents: " + contents);
    }
};

reader.readAsText(file);

下节

FileReader 对象有很多的功能的对象的集合,大部分都和 XMLHttpRequest 类似。通过这三篇文章,应该能够通过 javascript 读取文件并把文件发送到服务器了。 但是 File API 系统,要比上面这三篇文章讲到的多得多,下篇文章将会有更给力的功能来进行讨论。

这是一系列对于初学者的 javascript 文章,包括很多的基础知识,由于我也是初学者,所以,翻译过来,大家一起学习,这一系列就叫做 javascript 101 吧,嘿嘿,当然,一般情况下,每篇文章,基本不会超过十分钟。

经过多年对面向对象编程语言的学习,类(Class)的概念已经深深的在脑海中扎根,但是对于 javascript 却有点让人沮丧,因为 javascript 没有”类”的概念,但是,对于继承来说,javascript 有它自己的一套方式,甚至有时候比基于类的语言更具灵活性。

首先要认识到的就是明白基于对象的和基于类的语言的区别,而 javascript 给了我们使用类似“类”的语言能做的工作的大部分功能,也就是说,javascript 的继承方式,更类似与基于类的语言。

首先我们简单看一下 prototype 属性,看看它如何使我们更深入理解 javascript 。

prototype 属性

prototype 属性是一个内部属性,它的作用就是被设计用来实现继承。这里说的继承,有一个具体的形式,就是说,某个对象(例如 对象 b)的状态和方法都是由另一个对象(例如对象 a)产生的,因此,这里(对象 b)的结构,行为和状态,都是被继承的(继承于对象 a)。(ES5: Objects),这也是基于类的语言所有的,实例(instance)具有状态,而类具有方法。

构造函数(constructor)也是一个函数,有一个名为原型(prototype)的属性:

function Animal() {
}

console.log(Animal.prototype);

在这里会显示 {} 或者 object ,证明 Animal 对象具有 prototype 属性。但是,我们还没有在函数体内定义任何东西,然后增加一些代码:

function Animal() {
}

Animal.prototype.type = 'Unknown';
Animal.prototype.weight = 0;
Animal.prototype.weightUnits = 'kg';

Animal.prototype.toString = function() {
  return this.type + ', ' + this.weight + this.weightUnits;
};

var molly = new Animal();
molly.type = 'Dog';
molly.weight = 28;

console.log(molly.toString());

此时,将会显示 “Dog, 28kg”,同样的,此代码可以被改写,将其写到一起:

function Animal() {
}

Animal.prototype = {
  type: 'Unknown',
  weight: 0,
  weightUnits: 'kg',

  toString: function() {
    return this.type + ', ' + this.weight + this.weightUnits;
  }
};

这些和已经熟悉的基于类的语言,看起来应该没有什么太大的不同。

动态原型

可以动态的添加属性,只要赋值即可。

var molly = new Animal(), harley = new Animal();

molly.type = 'Dog';
molly.weight = 28;

harley.type = 'Dog';
harley.weight = 38;
harley.name = 'Harley';

console.log(molly);// { type: 'Dog', weight: 28 }
console.log(harley);// { type: 'Dog', weight: 38, name: 'Harley' }

此处添加的 “name” 属性,只影响了,(当前的)实例,但是,构造函数(constructor)的原型(prototype),同样可以被改变, 但是这样的话,就会影响所有继承此原型的对象了。例如:

var molly = new Animal();

Animal.prototype.weightUnits = 'oz';

molly.type = 'Dog';
molly.weight = 28;

console.log(molly.toString());//'Dog, 28oz'

不过,这样做(改变原型)有可能带来不好的结果。这也是很多类库不会这么做的原因,因为这么做可能更改本地内建函数的功能(浏览器自身),例如:

String.prototype.match = function() {
  return true;
};

console.log('alex'.match(/1234/));

此时,不管我怎么使用 match() 方法,其都会返回 true ,因此虽然成功的改写了此函数,但是也破坏了浏览器自身(原生的) javascript 的功能。

但是,这并不一定是坏事,因为我们总需要改写或者扩展一些原生的 javascript 功能,例如:使旧浏览器支持新的 javascript 标准。

如果改写了原生的 prototype 属性,会发生什么呢?

var molly = new Animal(), harley;

molly.type = 'Dog';
molly.weight = 28;

Animal.prototype = {
  toString: function() { // 改写原生的 toString 方法。
    return '...';
  }
};

harley = new Animal;
harley.type = 'Dog';
harley.weight = 38;

console.log(molly.toString());// Dog, 28kg
console.log(harley.toString());// ...

在 molly 时,并没有改写原生的 toString 方法,所以其正常输出,但是到 harley 时,由于改写了其方法,所以,其输出 “…” 省略号。

尽管在更改了 prototype 的情况下,应该影响所有的实例,但是,更改(重写)了构造函数的 prototype 却并不影响到所有的实例。这是因为,所有的实例都引用自原型,而不是原型的一份拷贝,也可以这么说:使用 new 关键字,创建的实例,都会连接到原始的原型。

一片枯黄的冬日伴随着春日逐渐返青的草地,像年幼的孩子一般,一股脑的消失在眼前,又像风骚的少妇般,扭着令人心动的丰臀,渐行渐远,慢慢的消失不见;只希望更加温暖的春日阳光早早来临,就像孩子们清澈透亮的笑声一样,传遍大街小巷,人们再也不用蜷缩在厚厚的衣物后面,悄悄的探出一只眼观看这个冰冷又死气沉沉的世界。

或许那样,当春日温暖的阳光照进我寒冷心房的时候,我就会幸福起来;当内心一个个枯黄的小草被青绿的颜色取代的时候,我就会幸福起来。当漫山遍野山花烂漫的时候,我就会幸福起来。

是否,我会真的幸福起来吗?

没有你的日子,或许还一如平常,只是偶尔溢出来的思念,谁人不知,却已被寂寞吞噬。

或许真的一如平常?还只是自欺欺人。那又为何每次走过曾经一起来的路,总会恍若隔世,却又好似昨日。

六年的时间,当思念像毛线般,逐渐绕成一个圈,我出不来,谁人也进不去。

忽然想到,你说过我是一个那么被动的孩子,我就想起来了小时候,舅舅为了我们的安全不让我们下河玩,而每当我们不听话下河被发现之后,舅舅就会用粉笔在地上画个圈,让我们都不准出去,而最后,就只有我一个人被一个粉笔画的圈给禁锢住。被别人笑,我却沉默,或许那时候就有骨子里的认真劲,答应了,就不放弃。答应了,就要做到。

可以,或许我终究还是放弃了?还是……

有时候,是不想出去,有时候是怎么也出不去。

一辈子,零零总总,还有那么多日子,有一个你;人海穿梭中,背对背的感情,却早已渐行渐远,消失不见。一辈子,不多不少,剩余了那么多的日子,没有你,茫茫前行中,背对背的感情,却一点没有减少,完好如初。

人海茫茫,看别人的感情,来来往往,熙熙攘攘,多拥挤,脚不停,心也不曾停下,身边更变换不停,难道唯一就可以动摇得那么轻易?或者,是他们醉酒没醒。还是我们在做梦?

不过,都已不再重要!

不是吗?

你要过上你好的生活,我要过我想要的生活,从一开始,6年前的你,6年后的我,或者都已注定。眼睛一闭,眼睛一睁,恍若隔世,却一辈子……

不过,你一定要幸福,我给不了你,你要给自己!