精华内容
下载资源
问答
  • Nginx断点续传功能代码浅析-Range模块

    千次阅读 2016-11-15 17:53:40
    Content-Range允许一次只下载一个文件的一部分,后面再分批次下载文件的其他部分,或者并发下载,提高下载速度,这样如果在下载一个文件的过程,网络断开了,恢复后不需要重新下载。 nginx 对Content-Range的支持...

    HTTP 的Content-Range支持对于一般的网页处理没啥重要的作用,但是对于大文件的下载,CDN回源,点续传功能的作用是非常重要的。

    Content-Range允许一次只下载一个文件的一部分,后面再分批次下载文件的其他部分,或者并发下载,提高下载速度,这样如果在下载一个文件的过程中,网络断开了,恢复后不需要重新下载。

    nginx 对Content-Range的支持包括header处理和body处理,分别用来解析客户端发送过来的Range header 和裁剪返回给客户端的请求数据Body。其实现分别由2个filter过滤模块完成,分别是ngx_http_range_header_filter_module和ngx_http_range_body_filter_module。下面分别介绍。

    零、解析客户端发送的请求Range头

    这部分很简单,nginx通过在ngx_http_headers_in请求头处理数组中增加对Range,If-Range的处理函数,从而读取到客户端发送的HEADER的时候,调用对应的函数,将请求数据设置到对应的字段上去。比如对于Range,处理函数为ngx_http_process_header_line ,Range值会放入ngx_http_headers_in_t 的 range字段上面。以便后续filter模块去处理。

    1ngx_http_headers_in
    2    { ngx_string("Range"), offsetof(ngx_http_headers_in_t, range),
    3                 ngx_http_process_header_line },
    4 
    5    { ngx_string("If-Range"),
    6                 offsetof(ngx_http_headers_in_t, if_range),
    7                 ngx_http_process_unique_header_line },

    一、Header 过滤函数

    ngx_http_range_header_filter_module模块回调函数中,只设置了一个ngx_http_range_header_filter_init,用来在nginx filter链表中挂载当前模块的header filter函数。这里说一下nginx对于filter函数链表的组织形式,相当巧妙:利用static 文件局部变量来保存下一个链表节点元素,类似堆栈的方式一个个将后面链接的文件组织起来,达到代码非常简单优美的效果。看下面就知道了。

    ngx_http_top_header_filter为全局变量,对于ngx_http_range_filter_module.c文件来说,是static 全局变量,也就是只在本文件有效。多个filter里面各有一份这样的static变量。

    1//静态局部变量,用来保存下一个过滤器的函数指针。nginx通常做法,用这种方式来建立一调filter链
    2static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
    3 
    4static ngx_int_t
    5ngx_http_range_header_filter_init(ngx_conf_t *cf)
    6{//组成filter链表
    7    ngx_http_next_header_filter = ngx_http_top_header_filter;
    8    ngx_http_top_header_filter = ngx_http_range_header_filter;
    9    return NGX_OK;
    10}
    11 
    12ngx_int_t
    13ngx_http_send_header(ngx_http_request_t *r)
    14{
    15    if (r->err_status) {//如果有错误发生,则发送错误状态。
    16        r->headers_out.status = r->err_status;
    17        r->headers_out.status_line.len = 0;
    18    }
    19    //第一个是ngx_http_not_modified_header_filter,后面一个个往后掉用。每个函数独立一个文件,那里面有个静态变量记录它的上一个filter是谁。
    20    return ngx_http_top_header_filter(r);//最后一个是ngx_http_header_filter_module模块里面的发送模块
    21}

    这样,在nginx需要给客户端发送头部header的时候,会调用ngx_http_send_header函数,而其代码很简单,只是调用了ngx_http_top_header_filter,因此,如果header range filter是最后一个挂载的header filter模块的话,ngx_http_top_header_filter链表头节点就等于ngx_http_range_header_filter,也就是最先调用ngx_http_range_header_filter函数进行header过滤处理。

    ngx_http_range_header_filter函数用来给客户端返回的header中插入”Accept-Ranges:bytes”头,以及解析客户端发送过来的Range头,其首先检查一下客户端是否支持Range,如果发送了If-Range,则需要检查一下last_modified_time的时间,是否相等,如果不相等,表示服务器的文件比客户端已经下载的不一样了,需要重新下载全部文件。

    1static ngx_int_t
    2ngx_http_range_header_filter(ngx_http_request_t *r)
    3{//ngx_http_top_header_filter链的函数指针,用来过滤发送给客户端的头部数据。
    4//ngx_http_send_header函数会调用这里。
    5    time_t                        if_range;
    6    ngx_int_t                     rc;
    7    ngx_http_range_filter_ctx_t  *ctx;
    8 
    9    if (r->http_version < NGX_HTTP_VERSION_10
    10        || r->headers_out.status != NGX_HTTP_OK
    11        || r != r->main
    12        || r->headers_out.content_length_n == -1
    13        || !r->allow_ranges)
    14    {//不处理非200的请求,否则直接调用下一个。
    15        return ngx_http_next_header_filter(r);
    16    }
    17 
    18    if (r->headers_in.range == NULL
    19        || r->headers_in.range->value.len < 7
    20        || ngx_strncasecmp(r->headers_in.range->value.data,
    21                           (u_char *) "bytes=", 6)
    22           != 0)
    23    {//如果客户端发送过来的头部数据中没有range字段,则我们也不需要处理range。直接返回即可
    24        goto next_filter;
    25    }
    26 
    27    if (r->headers_in.if_range && r->headers_out.last_modified_time != -1) {
    28        //语法为If-Range = "If-Range" ":" ( entity-tag | HTTP-date )  后面带的是时间,也就如果修改时间等于XX的话,就给我我没有的,否则给我所有的。
    29        if_range = ngx_http_parse_time(r->headers_in.if_range->value.data, r->headers_in.if_range->value.len);
    30        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
    31                       "http ir:%d lm:%d", if_range, r->headers_out.last_modified_time);
    32//If-Range的意思是:“如果entity没有发生变化,那么把我缺失的部分发送给我。
    33//如果entity发生了变化,那么把整个entity发送给我”。
    34        if (if_range != r->headers_out.last_modified_time) {
    35            goto next_filter;//时间跟服务器上的时间不相等,需要返回所有的。所以略过
    36        }
    37    }

    之后便需要解析Range头了。解析后的开始-结束区块会放入ctx->ranges数组中,用来在发送body的时候拼接http multipart 数据。这个工作主要由ngx_http_range_parse函数进行,其会解析字符串的Range格式数据,”Range: bytes=0-1024″请求一块 , “Range:bytes=500-600,601-999”代表客户端请求2块。ngx_http_range_parse不多介绍了,其主要就是做字符串解析。结果会放到ctx->ranges里面。后面拼接返回数据的时候会需要的。

    解析请求头完成后,需要准备返回给客户端的头部数据,其实这里还不能拼接返回数据,只是准备了一下相关结构。如果客户端只请求了一块,则由ngx_http_range_singlepart_header完成,否则由ngx_http_range_multipart_header完成。

    1//解析range的语法,在本文件开头有介绍。解析出开始,结束等。结果range放入ctx->ranges里面
    2    rc = ngx_http_range_parse(r, ctx);
    3 
    4    if (rc == NGX_OK) {
    5        ngx_http_set_ctx(r, ctx, ngx_http_range_body_filter_module);
    6        //修改发送的头部字段为206
    7        r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
    8        r->headers_out.status_line.len = 0;
    9 
    10        if (ctx->ranges.nelts == 1) {//单个RANGE
    11            return ngx_http_range_singlepart_header(r, ctx);
    12        }
    13        //多个range,比如"Range: bytes=500-600,601-999"
    14        return ngx_http_range_multipart_header(r, ctx);
    15    }
    16 
    17    if (rc == NGX_HTTP_RANGE_NOT_SATISFIABLE) {
    18        return ngx_http_range_not_satisfiable(r);
    19    }

    对于单个range请求,比较简单,只需要在返回的头部增加“Content-Range: bytes START-END/SIZE”头,数据就放在body里面即可,不需要用multipart的方式组织数据。单个range请求的返回数据形如:

    * “HTTP/1.0 206 Partial Content” CRLF
    * … header …
    * “Content-Type: image/jpeg” CRLF
    * “Content-Length: SIZE” CRLF
    * “Content-Range: bytes START-END/SIZE” CRLF
    * CRLF
    * … data …

    1static ngx_int_t
    2ngx_http_range_singlepart_header(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx)
    3{//客户端只请求了一个range,那么我们返回的数据格式为: Content-Range: bytes START-END/SIZE" CRLF
    4    ngx_table_elt_t   *content_range;
    5    ngx_http_range_t  *range;
    6 
    7    content_range = ngx_list_push(&r->headers_out.headers);//在输出头里面申请一个header line
    8    if (content_range == NULL) {
    9        return NGX_ERROR;
    10    }
    11    r->headers_out.content_range = content_range;
    12    content_range->hash = 1;
    13    ngx_str_set(&content_range->key, "Content-Range");
    14 
    15    content_range->value.data = ngx_pnalloc(r->pool, sizeof("bytes -/") - 1 + 3 * NGX_OFF_T_LEN);//申请三个足够长度的数字的字符串
    16    if (content_range->value.data == NULL) {
    17        return NGX_ERROR;
    18    }
    19    /* "Content-Range: bytes SSSS-EEEE/TTTT" header */
    20    range = ctx->ranges.elts;
    21//设置START-END/SIZE格式。
    22    content_range->value.len = ngx_sprintf(content_range->value.data,
    23                                           "bytes %O-%O/%O",
    24                                           range->start, range->end - 1, r->headers_out.content_length_n) - content_range->value.data;
    25    r->headers_out.content_length_n = range->end - range->start;//本次返回的数据长度。总长度在SIZE上面
    26//·····
    27    return ngx_http_next_header_filter(r);
    28}

    但是对于一次请求多个区块的请求,其处理相对复杂。因为返回的body数据无法直接放在BODY里面发送,需要组织成multipart 的形式才行了。这个由ngx_http_range_multipart_header函数搞定。先看一下多个range请求时返回数据的格式:

    “HTTP/1.0 206 Partial Content” CRLF
    … header …
    Content-Type: multipart/byteranges; boundary=0123456789″ CRLF
    CRLF//头部结束
    CRLF
    “–0123456789″ CRLF
    “Content-Type: image/jpeg” CRLF
    “Content-Range: bytes START0-END0/SIZE” CRLF
    CRLF
    … data …
    CRLF
    “–0123456789″ CRLF
    “Content-Type: image/jpeg” CRLF
    “Content-Range: bytes START1-END1/SIZE” CRLF
    CRLF
    … data …
    CRLF
    “–0123456789–” CRLF

    ngx_http_range_multipart_header函数首先调用ngx_next_temp_number获取一个临时数字当做boundary,然后组成boundary_header,也就是上面黄色背景的部分,这部分是通用的,可以在组成给客户端的返回数据的时候重复使用。

    1static ngx_int_t
    2ngx_http_range_multipart_header(ngx_http_request_t *r,
    3    ngx_http_range_filter_ctx_t *ctx)
    4{
    5    size_t              len;
    6    ngx_uint_t          i;
    7    ngx_http_range_t   *range;
    8    ngx_atomic_uint_t   boundary;
    9 
    10    len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN
    11          + sizeof(CRLF "Content-Type: ") - 1
    12          + r->headers_out.content_type.len
    13          + sizeof(CRLF "Content-Range: bytes ") - 1;
    14 
    15    if (r->headers_out.charset.len) {
    16        len += sizeof("; charset=") - 1 + r->headers_out.charset.len;
    17    }
    18 
    19    ctx->boundary_header.data = ngx_pnalloc(r->pool, len);
    20    if (ctx->boundary_header.data == NULL) {
    21        return NGX_ERROR;
    22    }
    23    boundary = ngx_next_temp_number(0);
    24    /*
    25     * The boundary header of the range:
    26     * CRLF
    27     * "--0123456789" CRLF
    28     * "Content-Type: image/jpeg" CRLF
    29     * "Content-Range: bytes "
    30     */
    31//拼接bondery头,也就是--xxxxx以及后面的那几行头部数据。头部数据之后就是bonder的数据部分了。
    32    if (r->headers_out.charset.len) {
    33        ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
    34                                           CRLF "--%0muA" CRLF
    35                                           "Content-Type: %V; charset=%V" CRLF
    36                                           "Content-Range: bytes ",
    37                                           boundary,
    38                                           &r->headers_out.content_type,
    39                                           &r->headers_out.charset)
    40                                   - ctx->boundary_header.data;
    41 
    42        r->headers_out.charset.len = 0;
    43 
    44    }
    45//···

    然后组成”Content-Type: multipart/byteranges; boundary=”这一行头部数据,放到r->headers_out.content_type上面。

    1//这是最开始那一行声明下面是mutipart 格式数据的行:"Content-Type: multipart/byteranges; boundary=0123456789" CRLF
    2//下面的sizeof其实没必要用全部字符串,Content-Type:不需要。
    3    r->headers_out.content_type.data =
    4        ngx_pnalloc(r->pool, sizeof("Content-Type: multipart/byteranges; boundary=") - 1 + NGX_ATOMIC_T_LEN);
    5 
    6    if (r->headers_out.content_type.data == NULL) {
    7        return NGX_ERROR;
    8    }
    9    r->headers_out.content_type_lowcase = NULL;
    10    /* "Content-Type: multipart/byteranges; boundary=0123456789" */
    11    r->headers_out.content_type.len =
    12                           ngx_sprintf(r->headers_out.content_type.data,
    13                                       "multipart/byteranges; boundary=%0muA", boundary)
    14                           - r->headers_out.content_type.data;
    15 
    16    r->headers_out.content_type_len = r->headers_out.content_type.len;

    然后就需要一个个将range的开始,结束部分字符串进行拼接了,也就是”SSSS-EEEE/TTTT” CRLF CRLF”,结果放到range[i].content_range.data上面,用来在后面body过滤函数里面组成返回body。之后就是调用下一个header filter过滤哈数ngx_http_next_header_filter。

    到这里头部数据的过滤函数完毕了,总结一下就是:

    1.调用ngx_http_range_parse解析客户端请求头:“Range: bytes=0-1024”。
    2.如果是单个Range,调用ngx_http_range_singlepart_header拼接”Content-Range: bytes START-END/SIZE”头。
    3.如果是多个Range请求,调用ngx_http_range_multipart_header准备multipart数据,以便后面body filter使用。

    二、BODY 过滤函数

    类似header过滤函数的处理方式,nginx挂载了body过滤函数ngx_http_range_body_filter,用来在ngx_http_output_filter里面需要给客户端发送HTTP BODY的时候调用。

    当该函数很简单,就是判断一下ctx->ranges.nelts的数量,如果是单个Range请求,就调用ngx_http_range_singlepart_body完成工作,否则判断一下是否所有的ranges都在第一块buf里面,nginx目前只支持所有的ranges都在一块buf里面的情况,并且也只支持所有的都在buf链表的的一块buf里面。所以支持的比较鸡肋。如果条件满足,就调用ngx_http_range_body_filter去处理。

    1static ngx_int_t
    2ngx_http_range_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
    3{//这是第一个BODY过滤函数
    4    ngx_http_range_filter_ctx_t  *ctx;
    5 
    6    if (in == NULL) {
    7        return ngx_http_next_body_filter(r, in);
    8    }
    9    ctx = ngx_http_get_module_ctx(r, ngx_http_range_body_filter_module);
    10    if (ctx == NULL) {
    11        return ngx_http_next_body_filter(r, in);
    12    }
    13    if (ctx->ranges.nelts == 1) {//如果只有一个range
    14        return ngx_http_range_singlepart_body(r, ctx, in);
    15    }
    16    /*下面说明,NGINX只支持整个数据都在一个buffer里面的情况也就是多个body它不好处理
    17     * multipart ranges are supported only if whole body is in a single buffer
    18     */
    19    if (ngx_buf_special(in->buf)) {
    20        return ngx_http_next_body_filter(r, in);
    21    }
    22    //检查是否所有的RANGE都在第一块buf里面。那么在后面的buf里面不行么,不行,nginx不支持
    23    if (ngx_http_range_test_overlapped(r, ctx, in) != NGX_OK) {
    24        return NGX_ERROR;
    25    }
    26 
    27    return ngx_http_range_multipart_body(r, ctx, in);
    28}

    单个range的情况由ngx_http_range_singlepart_body完成,其相对也简单,循环扫描每个in参数的buf链表,如果其在range里面,就将其放到out的链表中,组成一个新的链表,然后调用下一个filter。下面看一下有改动的函数。

    1static ngx_int_t ngx_http_range_singlepart_body(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
    2{//处理客户端只发送一个range过来的情况
    3    off_t              start, last;
    4    ngx_buf_t         *buf;
    5    ngx_chain_t       *out, *cl, **ll;
    6    ngx_http_range_t  *range;
    7 
    8    out = NULL;
    9    ll = &out;
    10    range = ctx->ranges.elts;
    11//对in参数的要发送出去的缓冲数据链表,一一检查其内容是否在range的start-end之间,
    12//不在的就丢弃,只剪裁出区间之内的,发送给客户端。
    13    for (cl = in; cl; cl = cl->next) {
    14 
    15        buf = cl->buf;
    16 
    17        start = ctx->offset;//在所有数据中的位置。
    18        last = ctx->offset + ngx_buf_size(buf);
    19 
    20        ctx->offset = last;
    21        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http range body buf: %O-%O", start, last);
    22        if (range->end <= start || range->start >= last) {//丢弃这个。将其pos指向last从而略过。
    23            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http range body skip");
    24            if (buf->in_file) {
    25                buf->file_pos = buf->file_last;
    26            }
    27            buf->pos = buf->last;
    28            buf->sync = 1;
    29            continue;
    30        }
    31        if (range->start > start) {//开头重合,去掉重合部分
    32            if (buf->in_file) {
    33                buf->file_pos += range->start - start;//向后移动开始部分
    34            }
    35            if (ngx_buf_in_memory(buf)) {//如果在内存的话就移动指针
    36                buf->pos += (size_t) (range->start - start);
    37            }
    38        }
    39 
    40        if (range->end <= last) {//尾部重合,去掉尾部
    41            if (buf->in_file) {
    42                buf->file_last -= last - range->end;
    43            }
    44            if (ngx_buf_in_memory(buf)) {
    45                buf->last -= (size_t) (last - range->end);
    46            }
    47            buf->last_buf = 1;//标记为最后一块内存,后面的都不需要了。
    48            *ll = cl;
    49            cl->next = NULL;//直接剪断,后面可能还有输出链的,但是没事,等这个连接关闭后,数据都回收了的。
    50            break;
    51        }
    52        *ll = cl;//后移动。
    53        ll = &cl->next;
    54    }
    55    //到这里后,out变量所指向的链表里面的数据都是range之间的,需要发送给客户端的数据。于是调用下一个filter进行发送。
    56    if (out == NULL) {
    57        return NGX_OK;
    58    }
    59    return ngx_http_next_body_filter(r, out);
    60}

    对于多个range的情况,首先由ngx_http_range_test_overlapped函数判断一下是否in参数的buf是最后一块,如果是最后一块那可以支持,否则,如果所有的ranges都在第一块buf里面,那也支持。其他都不能处理。
    ngx_http_range_multipart_body的工作是组成mutipart格式的数据,调用下一个过滤器发送给客户端。具体不啰嗦了。
    总结一下,nginx对于多个ranges的请求支持目前还不是让人满意,所以需要使用的同学建议只使用单个Range请求的方式。否则就会出现诡异的500错误而不得善终。不过好消息是:PHP没有cached的请求是不支持range的,image不支持,目前只有static请求是支持的。但是,static请求静态文件的话,一般都是整个文件都在一块buf结构里面,所以多个range也就一定在一块也是第一块buf里面,所以结论是:nginx 对range的支持能够满足大部分需求。


    原文出处: http://chenzhenianqing.cn/articles/926.html?utm_source=tuicool&utm_medium=referral

    展开全文
  • range函数的for循环1.定义2.两种形式3.可理解性例子4.range函数的特性详述4.1 左闭右开4.2 ...range是一个函数,它返回的是一个可迭代对象,大多使用于for循环。相当于C/Java 里面的 for (int i = m; i < n;...

    1.定义

    range是一个函数,它返回的是一个可迭代对象,大多使用于for循环中。相当于C/Java 里面的 for (int i = m; i < n; i++)循环

    2.两种形式

    range(stop)
    range(start, stop[, step])
    
    

    start作为开始值,开始值作为开始的那个数,不输入的话默认从0开始
    stop作为结束值,结束值所代表的不是结束的那个值,而是结束的那个下标,结束值的下标是从0开始算起。例如你输入5,那么输出之后就是4。(注意:结束值不可省略)
    step作为步进值,不进值代表一次你想要步进多少个,假如输入1的话,那输出之后就可以看到每一个数据之间相隔了1的差距,如果步进值省略的话,它的默认值依旧是1,只有当你自己输入其他的步进值,它才会改变。

    3.可理解性例子

    根据代码来理解range函数的重要值,将代码复制到你的pycharm中,就可以看到三个值的含义了
    代码

    #coding=utf-8
    
    import time
    
    #代表1到4,不包含4
    for i in  range(1, 4):
        print(i)
    #设置停顿三秒
    time.sleep(3)
    #代表1到10,间隔2,不包含10
    for i in range(1, 10, 2):
        print(i)
    time.sleep(3)
    #代表0到5,不包含5
    for i in range(5):
        print(i)
    time.sleep(3)
    
    

    4.range函数的特性详述

    4.1 左闭右开

    eg:
    代码:

    #1<=i<6
    for i in  range(1, 6):
        print(i)
    
    

    输出结果
    在这里插入图片描述
    从上图可知,1是取到的,而6是没有取到的,相当于数学中的左闭右开区间[1,6)

    4.2 开始值默认为0

    如果开始值不写的话,就会默认从0开始
    eg:
    代码

    #开始值不输入的话默认从0开始,打印输出0到9
    for i in  range(10):
        print(i)
    
    

    运行结果
    在这里插入图片描述

    4.3 步长值默认为1

    代码

    #步长值不输入的话默认为1
    for i in  range(1, 10):
        print(i)
    
    

    运行结果
    在这里插入图片描述
    当我们输入步长值为2的时候:
    代码

    #步长值输入为2
    for i in  range(1, 10, 2):
        print(i)
    
    

    运行结果
    在这里插入图片描述
    由上可知,步长值为2的时候,他的间隔就为2

    4.4 range函数的反向输出

    代码

    #打印输出10,9,8,7,6,5,4,3,2,1,0
    for i in range(10, -1, -1):
        print(i)
    
    

    运行结果
    在这里插入图片描述
    由上可知,range(10,-1,-1)反向输出10,9,8,7,6,5,4,3,2,1,0。此时的结束值为-1,而-1取不到,因此取到0,长是-1,相当于每次-1。

    5.与列表list的使用

    代码

    list1 = ["看不", "见你", "的", "笑", "我怎么", "睡", "得", "着"]
    for i in range(len(list1)):
        print(i, list1[i])
    

    运行结果
    在这里插入图片描述

    6.range与list的区别

    range()是依次取顺序的数值,常与for循环一起用,如for范围内的每个(0, 5):for循环执行5次,每个取值是0〜4
    而list()是把字符串转换为列表,如a = ’01234’ , b = list(a), a打印出来会是一个列表:[‘0’, ‘1’,‘2’,‘3’,‘4’], 如a = [0, 1, 2, 3, 4],输出的结果就会是[0, 1, 2, 3, 4]

    代码

    #对比range与list
    for i in range(0, 5):
        print(i)
    
    a = [0, 1, 2, 3, 4]
    print(a)
    
    

    运行结果
    在这里插入图片描述
    最后,希望可以帮到各位亲!
    在这里插入图片描述

    https://blog.csdn.net/hanhanwanghaha宝藏女孩 欢迎您的关注!
    欢迎关注微信公众号:宝藏女孩的成长日记
    让这个可爱的宝藏女孩在努力的道路上与你一起同行!
    如有转载,请注明出处(如不注明,盗者必究)

    展开全文
  • Range对象

    千次阅读 2016-09-29 09:43:37
    一、Range对象属性 属性值 说明 Range.collapsed ... 用于返回Range对象所代表的区域位于什么节点之,该属性值为包含了该区域的最低层节点(一个节点可能是一个元素,也可能是一段完整文字)的节点 Ra

    一、Range对象

    属性

    属性值说明
    Range.collapsed用于判断Range对象所代表的区域的开始点与结束点是否处于相同的位置,如果相同该属性值返回true
    Range.commonAncestorContainer用于返回Range对象所代表的区域位于什么节点之中,该属性值为包含了该区域的最低层节点(一个节点可能是一个元素,也可能是一段完整文字)的节点
    Range.endContainer用于返回Range对象所代表的区域的终点位于什么节点之中,该属性值为包含了该区域终点的最底层节点
    Range.endOffset用于返回Range对象所代表的区域的终点与包含该终点的节点的起点之间的距离
    Range.startContainer用于返回Range对象所代表的区域的起点位于什么节点之中,该属性值为包含了该区域起点的最底层节点
    Range.startOffset用于返回Range对象所代表的区域的起点与包含该起点的节点的起点之间的距离

    方法

    1、定位方法

    方法说明
    Range.setStart()用于将某个节点中的某处位置指定为Range对象所代表区域的起点位置
    Range.setEnd()用于将某个节点中的某处位置指定Range对象所代表区域的结束位置
    Range.setStartBefore()用于将某个节点的起点位置指定为Range对象所代表区域的起点位置
    Range.setStartAfter()用于将某个节点的终点位置指定为Range对象所代表区域的起点位置
    Range.setEndBefore()用于将某个节点的起点位置指定为Range对象所代表区域的终点位置
    Range.setEndAfter()用于将某个节点的终点位置指定为Range对象所代表区域的终点位置
    Range.selectNode()用于将Range对象的起点指定为某个节点的起点,将Range对象的终点指定为该节点的终点,使Range对象所代表的区域中包含该节点Range
    Range.selectNodeContents()用于将Range对象的起点指定为某个节点中的所有内容的起点,将Range对象的终点指定为该节点所有内容的终点,使Range对象所代表的区域中包含该节点的所有内容
    Range.collapse()向指定端点折叠该Range

    2、编辑方法

    方法说明
    Range.cloneContents()用于在页面上追加一段HTML代码,并且将Range对象所代表区域中的HTML代码克隆到被追加的html代码中
    Range.deleteContents()用于将Range对象中所包含的内容从页面中删除
    Range.extractContents()用于将Range对象所代表区域中的html代码克隆到一个DocumentFragment对象中,然后从页面中删除这段HTML代码
    Range.insertNode()用于将指定的节点插入到某个Range对象所代表的区域中,插入位置为Range对象所代表区域的起点位置
    Range.surroundContents()将 Range 的内容移动到一个新的节点中

    3、其他方法

    方法说明
    Range.compareBoundaryPoints()比较两个 Range 的端点。
    Range.cloneRange()返回拥有和原 Range 相同端点的克隆 Range 对象。
    Range.detach()从使用状态释放 Range,此方法用于改善性能
    Range.toString()把Range内容作为字符串返回

    4、Gecko方法

    在这里解释Mozilla独有的,在W3C DOM标准里没有的 Range方法

    方法说明
    Range.compareNode()返回常量,表示node是否在range的前、后、中、外
    Range.comparePoint()返回-1、0、1,分别表示指定点在range的前、中、后
    Range.createContextualFragment()解析指定字符串(XML或HTML),并返回document fragment
    Range.intersectsNode()返回boolean值,表示指定Node是否横断Range
    Range.isPointInRange()返回boolean值,表示指定点是否在range中

    document.selectiondocument.getSelection()/window.getSelection()分别为IEw3c浏览器 创建selection对象
    document.createTextRange()document.createRange()分别为IE和w3c浏览器创建range对象
    document.selection.createRange()window.getSelection().getRangeAt(0)是从selection对象获得range对象

    标准dom是从window中获取selection对象,而ie是从document对象中获取。
    标准dom range对象(以下称dom rang)和ieTextRange对象(以下称TextRange),在操作模式上有很大区别,可以说domrange是基于dom结构控制的,TextRange是基于文本节点字节控制的

    二、Range对象的概念

    Range对象代表页面上的一段连续区域,通过Range对象,可以获取或修改页面上的任何区域,可以通过如下创建一个空的Range对象,如下:
    var range = document.createRange()

    在html5中,每一个浏览器窗口及每一个窗口中都有一个selection对象,代表用户鼠标在页面中所选取的区域,(注意:经过测试IE9以下的浏览器不支持Selection对象), 可以通过如下语句创建selection对象

       var  selection = document.getSelection();
       var  selection  = window.getSelection();

    每一个 selection对象都有一个或者多个Range对象,每一个range对象代表用户鼠标所选取范围内的一段连续区域,在firefox中,可以通过 ctrl键可以选取多个连续的区域,因此在firefox中一个selection对象有多个range对象,在其他浏览器中,用户只能选取一段连续的区 域,因此只有一个range对象。
    可以通过selection对象的getRangeAt方法来获取selection对象的某个Range对象,如下:
    var range = document.getSelection().getRangeAt(index)

    function getRange(selection) {
        if (selection.getRangeAt) {
            return selection.getRangeAt(0);
        } else { //不支持getRangeAt的情况
            var range = document.createRange();
            range.setStart(selection.anchorNode, selection.anchorOffset);
            range.setEnd(selection.focusNode, selection.focusOffset);
            return range;
        }
    }

    getRangeAt()

    方法有一个参数index,代表该Range对象的序列号;我们可以通过Selection对象的rangeCount参数的值判断用户是否选取了内容;

    <h3>selection对象与range对象的使用实例</h3>
     <input type="button" value="点击我" onclick="rangeTest()"/>
     <div id="showRange"></div>
    function rangeTest() {
        var  html,
        showRangeDiv = document.getElementById("showRange"),
         selection = document.getSelection();
        if(selection.rangeCount > 0) {
            html = "你选取了" + selection.rangeCount + "段内容<br/>";
            for(var i = 0; i < selection.rangeCount; i++) {
                var range = selection.getRangeAt(i);
                html += "第" + (i + 1) + "段内容为:" + range + "<br/>";
            }
            showRangeDiv.innerHTML = html;
        }
    }

    上面的rangeObj代表一个Range对象,该方法使用一个参数,代表页面中的一个节点。

    selectNode()

    Range对象的selectNode方法用于将Range对象的起点指定为某个节点的起点,将Range对象的终点指定为该节点的终点,使Range对象所代表的区域中包含该节点
    rangeObj.selectNode(node);

    selectNodeContents()

    用于将Range对象的起点指定为某个节点中的所有内容的起点,将Range对象的终点指定为该节点所有内容的终点,使Range对象所代表的区域中包含该节点的所有内容
    rangeObj.selectNodeContents(node)

    deleteContents()

    用于将Range对象中所包含的内容从页面中删除
    rangeObj.deleteContents()

    <div id="div" style="background-color:#e0a0b0;width:300px;height:50px;">元素中的内容</div>
    <button onclick="deleteRangeContents(true)">删除内容</button>
    <button onclick="deleteRangeContents(false)">删除元素</button>
    function deleteRangeContents(flag) {
        var div = document.getElementById("div");
        var rangeObj = document.createRange();
        if(flag) {
            // selectNodeContents指选中Range对象中的所有内容  进行删除掉
            rangeObj.selectNodeContents(div);
            rangeObj.deleteContents();
        }else {
            rangeObj.selectNode(div);
            rangeObj.deleteContents();
        }
    }

    setStart()

    用于将某个节点中的某处位置指定为Range对象所代表区域的起点位置
    rangeObj.setStart(node,curIndex)
    该setStart方法使用2个参数,第一个参数node代表一个节点,第二个参数是一个数字,当第一个参数 node所代表的节点是一个内容为一段文字的文字节点时,该参数值用于指定将第几个文字的结束位置作为Range对象所代表的区域的起点位置;当第一个参 数node所代表的节点中包括其他子节点时,该参数值用于将第几个子节点的结束位置指定为Range对象所代表的区域的起点位置;

    setEnd()

    用于将某个节点中的某处位置指定Range对象所代表区域的结束位置
    rangeObj.setEnd(node,curIndex)

    该方法中的2个参数的含义如setStart方法中参数的含义相同;只不过一个是起点位置,另一个是结束位置;

    <div id="myDiv">这段文字中第三个文字到第十个文字将被删除掉</div>
     <button onclick="DeleteChar()">删除文字</button>
        function DeleteChar(){
            var myDiv = document.getElementById("myDiv");
            var textNode = myDiv.firstChild;
            var rangeObj = document.createRange();
            rangeObj.setStart(textNode,2);
            rangeObj.setEnd(textNode,10);
            rangeObj.deleteContents();
        }

    setStartBefore()

    用于将某个节点的起点位置指定为Range对象所代表区域的起点位置
    rangeObj.setStartBefore(node)

    setStartAfter()

    用于将某个节点的终点位置指定为Range对象所代表区域的起点位置
    rangeObj.setStartAfter(node)

    setEndBefore()

    用于将某个节点的起点位置指定为Range对象所代表区域的终点位置
    rangeObj.setEndBefore(node)

    setEndAfter()

    用于将某个节点的终点位置指定为Range对象所代表区域的终点位置
    rangeObj.setEndAfter(node)

    我们还是先来做一个demo,来理解下上面的四个方法;如下所示:

    <table id="myTable" border = "1" cellspacing="0" cellpadding="0">
         <tr>
             <td>第一行第一列</td>
             <td>第一行第二列</td>
         </tr>
         <tr>
             <td>第二行第一列</td>
             <td>第二行第二列</td>
         </tr>
     </table>
     <button onclick="deleteFirstRow()">删除第一行</button>
    function deleteFirstRow(){
        var myTable = document.getElementById("myTable");
        if(myTable.rows.length > 0){
            var row = myTable.rows[0];
            var rangeObj = document.createRange();
            rangeObj.setStartBefore(row);
            rangeObj.setEndAfter(row);
            rangeObj.deleteContents();
        }
    }

    cloneRange()

    Range对象的cloneRange方法用于对当前的Range对象进行复制,该方法返回复制的Range对象
    var rangeClone = rangeObj.cloneRange()

    <div id="test">aaaa</div>
    <button onclick="cloneARange()">克隆Range对象</button> 
    function cloneARange() {
        var testObj = document.getElementById("test");
        var rangeObj = document.createRange();
        rangeObj.selectNodeContents(testObj);
        var rangeClone = rangeObj.cloneRange();
        alert(rangeClone);
    }

    cloneContents()

    该方法用于在页面上追加一段HTML代码,并且将Range对象所代表区域中的HTML代码克隆到被追加的html代码中
    var html = rangeObj.cloneContents()

    该方法不使用任何参数,该方法返回一个DocumentFragment对象,该对象为一个容器元素,当需要追加,删除,修改或查找页面上的元素,该对象变得非常有用;

    <div id="div">实例文字<br/>
        <button onclick="cloneDivContent()">克隆</button>
    </div>
    function cloneDivContent() {
        var div = document.getElementById("div");
        var rangeObj = document.createRange();
        rangeObj.selectNodeContents(div);
        var documentFragment = rangeObj.cloneContents();
        div.appendChild(documentFragment);
    }

    extractContents()

    用于将Range对象所代表区域中的html代码克隆到一个DocumentFragment对象中,然后从页面中删除这段HTML代码
    var documentFragment = rangeObj.extractContents()

    该方法返回一个包含了Range对象所代表区域中的HTML代码的DocumentFragment对象

    <div id="srcDiv" style="width:300px;height:50px;background-color:red;">demo</div>
    <div id="destDiv" style="width:300px;height:50px;background:blue;">demo2</div>
    <button onclick = "moveContent()">移动元素内容</button>
    function moveContent() {
        var srcDiv = document.getElementById("srcDiv");
        var destDiv = document.getElementById("destDiv");
        var rangeObj = document.createRange();
        rangeObj.selectNodeContents(srcDiv);
        var documentFragment = rangeObj.extractContents();
        destDiv.appendChild(documentFragment);
    }

    insertNode()

    该方法用于将指定的节点插入到某个Range对象所代表的区域中,插入位置为Range对象所代表区域的起点位置,如果该节点已经存在于页面中,该节点将被移动到Range对象代表的区域的起点处。
    rangeObj.insertNode(node)

    <div onmouseup="MoveButton()" style="width:400px;height:100px;background-color:red">aaaaaaaaabbbbbb</div>
    <button id="button">按钮</button>
    function MoveButton() {
        var button = document.getElementById("button");
        var selection = document.getSelection();
        if(selection.rangeCount > 0) {
            var range = selection.getRangeAt(0);
            range.insertNode(button);
        }
    }

    三、TextRange对象

    TextRange对象主要使用以下方法控制区域的选择
    1、moveStart
    2、moveEnd
    3、move
    这三个函数使用相同的参数语法:fn(sUnit [, iCount])
    第一个参数是指移动的单位,可以使用的参数:character(字符)、word(词)、sentence(段落)、textedit(整个编辑区)
    第二个参数指移动的数量单位,负数向所在位置之前移动,正数向所在位置之后移动。
    在实际开发中一般使用character,其他几个参数在中文环境和html编辑时,和预想位置有偏差。

    例1:TextRange选择光标前4个字符

    var rg=document.selection.createRange(); 
    rg.moveStart("character",-4); 
    rg.select();//显式选择文本区域,不调用此函数也可以获得选择的内容 
    var text=rg.text;//获得选择的文本 
    var htmlText=rg.htmlText;//获得选择文本的html代码 

    注:用rg.htmlText获得选择文本的html代码,但获得结果不尽人意,
    如:<b>aaaa</b>bb ,当选择aabb段时,.htmlTex返回的是<b>aa</b>bb而不是aa</b>bb

    dom range对象选择区域主要以dom节点为为坐标,所有边界移动和区域选择函数都是以dom节点为参照的.setEnd()setStart()是控制范围的前边界点和后边界点位置的函数,有两个参数,第一个参数是dom节点,第二个参数是偏移量,这个参数和TextRange.move中不同,是相对于dom节点的偏移量。

    如:有文字节点node1 nodeValue是aaabbbccc,setStart(node1,3)则设置开始位置在字符a、b之间。那如何像例1一样选择光标前4个字符呢,这需要了解dom range对象的几个属性:

    • endContainer 包含范围的结束点的 dom节点。
    • endOffset endContainer 中的结束点位置。
    • startContainer 包含范围的开始点的 dom节点。
    • startOffset startContainer 中的开始点位置。

    例2:dom range选择光标前4个字符

    var rg=window.getSelection().getRangeAt(0); 
    rg.setStart(rg.startContainer,rg.startOffset-4);//获得当前range strat所在节点和偏移量,计算后作为参数 
    //在调用setStart后即显式选择,与TextRange不同 
    var text=rg.toString();//获得选择文本 
    rg.collapse(false);//collapse函数与TextRange.collapse相同 
    <div id="ifram_div"></div>
    window.onload=function(){
        var iframeContent="";
        var divContent="";
        var divChange=false;
        var iframeChange=false;
        var $=function(s){return document.getElementById(s);}
        $("ifram_div").innerHTML+='<div id="infoIframe">iframe</div><iframe id="youretextarea" style="height:200px;width:99%;" class="_editbox"></iframe>';
        var fw=$("youretextarea").contentWindow;
        var f=fw.document;
        f.designMode = 'On';
        f.contentEditable = true;
        f.open();
        f.writeln('<html><style>p{margin:0px;padding:0px;}body{margin:0px;padding:0px;font:16/18px Arial;}</style><body><b>aaaa</b><u>bbbb</u>cccddd</body></html>');
        f.close();
        if(f.attachEvent){
            f.attachEvent("onkeyup",fun1);
        }else{
            fw.addEventListener("keyup",fun1,true);
        }
        function fun1(){
            if(f.selection){
                var rg=f.selection.createRange();
                rg.moveStart("character",-4);
    //rg.select();//显式选择文本区域,不调用此函数也可以获得选择的内容
                var text=rg.text;//获得选择的文本
                var htmlText=rg.htmlText;//获得选择文本的html代码
                alert(text);
            }else{
                var rg=fw.getSelection().getRangeAt(0);
                rg.setStart(rg.startContainer,rg.startOffset-4);//获得当前range strat所在节点和偏移量,计算后作为参数
    //在调用setStart后即显式选择,与TextRange不同
                var text=rg.toString();//获得选择文本
                rg.collapse(false);//collapse函数与TextRange.collapse相同
                alert(text);
            }
        }
    }

    例3:获得用户所选内容

    var userSelection;
    if (window.getSelection) { //现代浏览器
        userSelection = window.getSelection();
    } else if (document.selection) { //IE浏览器 考虑到Opera,应该放在后面
        userSelection = document.selection.createRange();
    }
    alert(userSelection)
    var selectedText = userSelection;
    if (userSelection.text) {
        selectedText = userSelection.text;
    }

    四、兼容

    RangeTextRange
    cloneRange()duplicate()
    setStart()moveStart()
    setEnd()moveEnd()
    selectNodeContents()moveToElementText()
    toString()text
    compareBoundaryPoints()compareEndPoints()
    展开全文
  • 理解Range对象

    千次阅读 2019-07-02 19:43:56
    1.理解Range对象 重新来学习下HTML5Range对象...在HTML5,一个Range对象代表页面上的一段连续区域。可以通过如下语句创建一个空的Range对象。如下代码: var range = document.createRange(); 什么是Selectio...

    1.理解Range对象

    重新来学习下HTML5中的Range对象和Selection对象,最近在维护富文本编辑器,感觉这方面的知识点很有用,所以趁着周末多学习下~

    什么是Range对象?
    在HTML5中,一个Range对象代表页面上的一段连续区域。可以通过如下语句创建一个空的Range对象。如下代码:

    var range = document.createRange();
    

    什么是Selection对象?
    在HTML5中,每一个浏览器窗口都有一个Selection对象,代表用户鼠标在页面中所选取的区域。可以通过如下代码得到一个Selection对象:

    var selection = window.getSelection();
    或 
    var selection = document.getSelection();
    

    Selection对象与Range对象的区别是?
    每一个Selection对象都有一个或多个Range对象,每一个Range对象代表用户用鼠标所选取范围内的一段连续区域。

    Firefox 与 chrome,safari对Selection的区别?
    在Firefox浏览器中,用户可以通过按住 ctrl键来选取页面上的多个区域,因此一个Selection对象可能有多个Range对象。
    在chrome或safari浏览器中,用户每次只能选取一段区域,所以一个Selection对象中只能有一个Range对象。

    如何获取Selection对象中的某个Range对象呢?
    可以通过Selection对象的getRangeAt方法来获取。代码如下:

    var range = document.getSelection().getRangeAt(rangeIndex);
    

    rangeIndex 代表Range对象的序号,在chrome或safari中,用户每次只能选取一段区域,因此该值只能为0;

    如何判断用户是否选取了内容?
    可以通过Selection对象的 rangeCount 属性来判断;
    \1. 用户没有按下鼠标该属性值为0;
    \2. 用户按下鼠标之后该属性值为1;
    \3. 用户用鼠标加ctrl键选取了一个或多个区域时,该属性值代表用户通过鼠标选取的区域的数量,当用户取消该区域的选取之后,该属性值为1.
    下面是一个demo;页面上显示一段文字和一个按钮,当用户单击按钮时弹出的提示框中显示用户用鼠标加ctrl键共选取了多少个区域及每一段区域中的内容。代码如下:

    [复制代码](javascript:void(0)?

    <!DOCTYPE html>
      <html>
        <head>
          <meta charset="utf-8">
          <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">
          <title>标题</title>
        </head>
        <body>
          <h3>Selection对象与Range对象使用demo</h3>
          <input type="button" value="选中我然后点击" onClick="rangeTest()" />
          <div id="showRange"></div>
          <script>
            function rangeTest() {
              var html,
                showRangeDiv=document.getElementById('showRange'),
                selection=document.getSelection();
              if(selection.rangeCount > 0) {
                html = "您选取了"+ selection.rangeCount + "段内容<br/>";
                for(var i = 0; i < selection.rangeCount; i++) {
                  var range = selection.getRangeAt(i);
                  html += "第"+ (i+1) + "段内容为:" + range + "<br/>"; 
                }
                showRangeDiv.innerHTML = html;
              }
            }
          </script>
        </body>
      </html>
    

    [复制代码](javascript:void(0)?

    查看效果

    1-2 Range对象的属性有哪些?
    我们在页面上创建的代码如下:

    var rangeObj = document.createRange();
    console.log(rangeObj);
    

    打印后看到有如下属性:
    collapsed: (Boolean) 用于判断Range对象所代表的区域的开始点和结束点是否位于相同的位置,如果相同该属性值为true。
    commonAncestorContainer: (node) 返回Range对象所代表的区域位于什么节点之中。
    endContainer: (node) 用于返回Range对象所代表的区域的终点位于什么节点之中。
    endOffset(int) 用于返回Range对象所代表区域的终点与包含该终点的节点起点之间的距离。
    startContainer: (node) 用于返回Range对象所代表区域的起点位于什么节点之中。
    startOffset (int) 用于返回Range对象所代表的区域的起点与包含该起点节点的起点之间的距离。

    2 Range对象的方法
    2-1 理解使用 selectNode, selectNodeContents, 与 deleteContents方法
    selectNode: Range对象的selectNode 方法用于将Range对象的起点指定为某个节点的起点,将Range对象的终点指定为该节点的终点。Range对象所代表的区域包含该节点。
    使用方法如下:
    rangeObj.selectNode(node);
    demo举例理解
    假如页面上有一个div,该div包含内容,如果使用 rangeObj.selectNode(“div”); 的含义是,选择该div内的所有内容,包括该div标签本身。

    selectNodeContents: 该方法用于将Range对象的起点指定为某个节点中的所有内容的起点,将Range对象的终点指定为该节点所有内容的终点,也就是说使Range对象所代表的区域包含该节点的所有内容,但是不包括该节点标签本身。

    使用方法如下:
    rangeObj.selectNodeContents(node);
    demo举例理解:
    还是上面的div元素,该元素包含内容,如果使用 rangeObj.selectNodeContents(‘div’);的话,的含义是,选择该div内的所有内容,但是不包括该div本身。

    deleteContents: 该方法用于将Range对象中所包含的内容从页面中删除。
    使用方法如下:
    rangeObj.deleteContents();

    下面是使用一个demo来理解上面的三个方法的具体含义,页面中有一个div元素,一个删除内容的按钮,和一个删除元素的按钮,div元素中显示一些文字,当用户单击 "删除内容"按钮时,会将div元素中的文字从页面中删除,单击 “删除元素” 按钮时,会将整个div元素从页面中删除。

    代码如下:

    [复制代码](javascript:void(0)?

    <!DOCTYPE html>
         <html>
            <head>
              <meta charset="utf-8">
              <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">
              <title>标题</title>
            </head>
            <body>
              <h3>Selection对象与Range对象使用demo</h3>
              <div id="div" style="background-color: #e0a0b0; width:300px; height: 50px;">aaaaadsadsdasadsbbgg</div>
              <button onclick="deleteRangeContents(true);">删除内容</button>
              <button onclick="deleteRangeContents(false);">删除元素</button>
    
              <script>
                  function deleteRangeContents(flag) {
                    var div = document.getElementById("div");
                    var rangeObj = document.createRange();
                    if (flag) {
                      rangeObj.selectNodeContents(div);
                    } else {
                      rangeObj.selectNode(div);
                    }
                    rangeObj.deleteContents();
                  }
              </script>
            </body>
        </html>
    

    [复制代码](javascript:void(0)?

    查看效果

    2-2 理解使用 setStart方法,setEnd方法,setStartBefore方法,setStartAfter方法,setEndBefore方法与setEndAfter方法

    setStart: 该方法用于将某个节点中的某个位置指定为Range对象所代表区域的起点位置。使用方法如下:
    rangeObj.setStart(node, num);
    num的含义是:首先它是一个整型数值,有两种含义取决于node节点;
    \1. 当第一个参数node所代表的节点是一个内容为一段文字的时候,那么该参数值用于指定将第几个文字结束位置作为Range对象所代表区域的起点位置(num是从0开始)
    \2. 当第一个参数node所代表的节点包括其他子节点时,该参数用于指定将第几个子节点的结束位置作为Range对象所代表区域的起点位置。

    setEnd: 该方法用于将某个节点中的某处位置指定为Range对象所代表区域的结束位置。使用方法如下:
    rangeObj.setEnd(node, num);
    num的含义: 首先是一个整型数值;
    \1. 当第一个参数node所代表的节点是一个内容为一段文字的时候,该参数指定将第几个文字结束位置作为Range对象所代表区域的结束位置。
    \2. 当第一个参数node所代表的节点包括其他子节点时,该参数值用于指定将第几个子节点的结束位置作为Range对象所代表区域的结束位置。

    下面是一个简单的demo来理解上面的 setStart和setEnd方法的使用,页面上有一个div元素和一个删除文字的按钮,div元素中有一些文字,当用户点击 删除文字按钮时,div元素中的第三个文字到第十个文字将被删除。
    代码如下:

    [复制代码](javascript:void(0)?

    <!DOCTYPE html>
         <html>
            <head>
              <meta charset="utf-8">
              <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">
              <title>标题</title>
            </head>
            <body>
              <div id="myDiv" style="color: red">这段文字中第三个文字到第十个文字将被删除</div>
              <button onclick="deleteChar()">删除文字</button>
              <script>
                function deleteChar() {
                  var div = document.getElementById("myDiv");
                  var textNode = div.firstChild;
                  var rangeObj = document.createRange();
                  rangeObj.setStart(textNode, 2);
                  rangeObj.setEnd(textNode, 10);
                  rangeObj.deleteContents();
                }
              </script>
            </body>
        </html>
    

    [复制代码](javascript:void(0)?

    查看效果

    setStartBefore: 该方法用于将某个节点的起始位置指定为Range对象所代表区域的起点位置。使用方法如下:
    rangeObj.setStartBefore(node);

    setStartAfter: 该方法用于将某个节点的终点位置指定为Range对象所代表区域的起点位置。使用方法如下:
    rangeObj.setStartAfter(node);

    setEndBefore: 该方法用于将某个节点的起始位置指定为Range对象所代表区域的终点位置,使用方法如下:
    rangeObj.setEndBefore(node);

    setEndAfter: 该方法用于将某个节点的终点位置指定为Range对象所代表区域的终点位置。使用方法如下:
    rangeObj.setEndAfter(node);

    看上面的四个方法容易混淆,我们来做一个demo,使用一下 setStartBefore 和 setEndAfter方法。 页面上有一个表格和一个按钮,用户单击按钮时,通过Range对象的setStartBefore 和 setEndAfter方法 使Range对象代表的区域包含表格的第一行,然后删除该行。

    [复制代码](javascript:void(0)?

    <!DOCTYPE html>
        <html>
          <head>
            <meta charset="utf-8">
            <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">
            <title>标题</title>
          </head>
          <body>
            <table id="myTable" border="1" cellspacing="0" cellpadding="0">
              <tr>
                <td>第一行第一列</td>
                <td>第一行第二列</td>
              </tr>
              <tr>
                <td>第二行第一列</td>
                <td>第二行第二列</td>
              </tr>
            </table>
            <button onclick="deleteFirstRow()">删除第一行</button>
            <script>
              function deleteFirstRow() {
                var myTable = document.getElementById('myTable');
                if (myTable.rows.length > 0) {
                  var row = myTable.rows[0];
                  var rangeObj = document.createRange();
                  rangeObj.setStartBefore(row);
                  rangeObj.setEndAfter(row);
                  rangeObj.deleteContents();
                }
              }
            </script>
          </body>
        </html>
    

    [复制代码](javascript:void(0)?

    查看效果

    2-3 理解使用 cloneRange方法,cloneContents方法,extractContents方法

    cloneRange: 该方法用于对当前的Range对象进行复制,该方法返回复制的Range对象。使用方法如下:
    var rangeClone = rangeObj.cloneRange();
    下面可以看一个demo来理解一下,页面上显示一个 “克隆Range对象” 按钮,用户单击该按钮时,创建一个Range对象,该对象包含页面中的所有内容,然后使用cloneRange方法复制Range对象,然后在弹窗显示该Range对象中的内容。
    代码如下:

    [复制代码](javascript:void(0)?

    <!DOCTYPE html>
       <html>
          <head>
            <meta charset="utf-8">
            <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">
            <title>标题</title>
          </head>
          <body>
            <button onclick="cloneRange()">克隆Range对象</button>
            <script>
              function cloneRange() {
                var rangeObj = document.createRange();
                rangeObj.selectNodeContents(document.body);
                var rangeClone = rangeObj.cloneRange();
                console.log(rangeClone);
                alert(rangeClone.toString());
              }
            </script>
          </body>
      </html>
    

    [复制代码](javascript:void(0)?

    查看效果

    cloneContents: 该方法用于在页面上追加一段HTML代码,使用方法如下:
    var docFragment = rangeObj.cloneContents();
    该方法返回的是 一个 DocumentFragment对象,该对象为一个容器元素,当需要追加,修改,删除或查找页面上的元素时,该方法非常有用。
    下面是一个demo,页面上显示一个div元素,div元素中包含一些文字和一个按钮,用户点击按钮时将在该div元素底部克隆出相同的文字和按钮。

    [复制代码](javascript:void(0)?

    <!DOCTYPE html>
       <html>
          <head>
            <meta charset="utf-8">
            <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">
            <title>标题</title>
          </head>
          <body>
            <div id="div">
              <span>aaaaaa</span>
              <button onclick="cloneDivContent()">克隆</button>
            </div>
            <script>
              function cloneDivContent() {
                var div = document.getElementById('div');
                var rangeObj = document.createRange();
                rangeObj.selectNodeContents(div);
                var documentFragment = rangeObj.cloneContents();
                div.appendChild(documentFragment);
              }
            </script>
          </body>
      </html>
    

    [复制代码](javascript:void(0)?

    查看效果

    extraContents: 该方法用于将Range对象所代表区域的HTML代码克隆到一个 DocumentFragment中,然后从页面中删除这段HTML代码;

    使用方法如下:
    var documentFragment = rangeObj.extraContents();

    下面是一个demo, 页面上有2个div元素和一个按钮,其中第一个div元素包含一些文字,用户单击该按钮时,把文字移动到第二个div中。

    [复制代码](javascript:void(0)?

    <!DOCTYPE html>
       <html>
          <head>
            <meta charset="utf-8">
            <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">
            <title>标题</title>
          </head>
          <body>
            <div id="srcDiv" style="background-color: red; width: 300px; height:50px">adsddasdssdsdsdsads</div>
            <div id="distDiv" style="background-color: blue; width: 300px; height: 50px;"></div>
            <button onclick="moveContent()">移动元素内容</button>
    
            <script>
              function moveContent() {
                var srcDiv = document.getElementById('srcDiv');
                var distDiv = document.getElementById("distDiv");
                var rangeObj = document.createRange();
                rangeObj.selectNodeContents(srcDiv);
                var documentFragment = rangeObj.extractContents();
                distDiv.appendChild(documentFragment);
              }
            </script>
          </body>
      </html>
    

    [复制代码](javascript:void(0)?

    查看效果

    2-4 insertNode方法
    该方法用于将指定的节点插入到某个Range对象所代表的区域中,插入位置为Range对象所代表区域的起点位置,如果该节点已经存在于页面之中,那么该节点
    将被移动到Range对象所代表区域的起点处。
    使用方法如下:
    rangeObj.insertNode(node)

    下面是一个demo,页面中有一个div元素和一个按钮,div元素有一些文字,在div元素中按下鼠标左键时,该按钮将被移动到按下鼠标左键的位置。

    [复制代码](javascript:void(0)?

    <!DOCTYPE html>
       <html>
          <head>
            <meta charset="utf-8">
            <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">
            <title>标题</title>
          </head>
          <body>
            <div onmouseup="moveButton()" style="width: 400px; background-color: red;">adssdasddsdasszczxccxzcx</div>
            <button id="button">按钮</button>
    
            <script>
              function moveButton() {
                var button = document.getElementById("button");
                var selection = document.getSelection();
                if (selection.rangeCount > 0) {
                  var range = selection.getRangeAt(0);
                  range.insertNode(button);
                }
              }
            </script>
          </body>
      </html>
    

    [复制代码](javascript:void(0)?

    查看效果

    2-5 理解collapse方法 和 detach方法
    该方法用于将Range对象所代表的区域的终点移动到该区域的起点处,或将Range对象所代表的区域的起点移动到终点处,使Range对象所代表的区域内不包含任何内容。
    使用方法如下:
    RangeObj.collapse(toStart);
    参数toStart,是一个Boolean型,如果为false的话,表示将Range对象所代表的区域的起点移动到终点处,当为true的话,表示将Range对象所代表的区域的终点移动到该区域的起点处。
    下面是一个demo,可以来理解下 collapse方法的使用;
    页面上有一个div元素,里面包含一些文字,一个 选择元素的 按钮,一个 取消选择元素的按钮,和一个 显示Range内容的按钮,用户单击 选择元素 按钮时该Range对象中将包含页面中的div元素,然后单击 显示Range内容按钮 就弹出 该div元素的内容,再单击 取消选择按钮 将使用Range对象的collapse方法清空Range对象的内容。再次单击 显示Range内容,将显示空字符串。
    代码如下:

    [复制代码](javascript:void(0)?

    <!DOCTYPE html>
           <html>
              <head>
                <meta charset="utf-8">
                <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">
                <title>标题</title>
              </head>
              <body>
                <div id="div" style="background-color: red; width:300px; height: 50px;">元素中的内容</div>
                <button onclick="selectRangeContents()">选择元素</button>
                <button onclick="unselect()">取消选择</button>
                <button onclick="showRange()">显示Range内容</button>
                <script>
                  var rangeObj = document.createRange();
                  function selectRangeContents() {
                    var div = document.getElementById('div');
                    rangeObj.selectNode(div);
                  }
                  function unselect() {
                    rangeObj.collapse(false);
                  } 
                  function showRange() {
                    alert(rangeObj.toString());
                  }
                </script>
              </body>
          </html>
    

    [复制代码](javascript:void(0)?

    查看效果

    detach方法: 该方法用于从浏览器中释放Range对象,释放之后将不能再访问该Range对象,否则将抛出脚本错误,使用方法如下:
    RangeObj.detach();

    展开全文
  • Excel中range和cells的详解

    万次阅读 多人点赞 2017-12-11 16:00:22
    1. 单元格Bi 可以使用以下3表示方法Range("B" &amp; i )Cells( i , 2 ) Cells( i , "B") (当列号较大、不易计算时,如“H"、”AD",我们可以直接用双引号加列标作为第二参数...
  • VB.net excel 的range方法

    千次阅读 2013-04-19 10:19:05
    Range对象可能是VBA代码中最常用的对象,Range对象可以是某一单元格、某一单元格区域、某一行、某一列、或者是多个连续或非连续的区域组成的区域。下面介绍Range对象的一些属性和方法。 - - - - - - - - - - - - - ...
  • 关于python代码中缩进的详解

    千次阅读 2020-11-23 11:06:01
    python的缩进是很严格的,python中用缩进来代表不同层级的代码块,同一层级的代码要对齐。那么什么是层级?什么代码块?下面就用代码示例给大家详细的介绍一下。 # 循环的缩进 sum_num = 0 for i in range(1, ...
  • bootstrapdaterangepicker组件的使用(二)

    万次阅读 2018-11-23 19:15:19
    为了实现 bootstrapdaterangepicker组件的使用(一) 的需求,我对demo进行了定制,具体需求可以参见:bootstrapdaterangepicker组件的使用(一) : ... 此demo的作用是根据两个变量动态算出默认的开始日期和结束日期...
  • NSGA2 算法MATLAB完整代码 中文注释详解

    万次阅读 多人点赞 2018-06-26 20:31:12
    2019.7.17 很意外本人这篇文章受到很多人的关注,在此把源码贴出来供大家更好的理解学习。 ... ========================分割==========================...本人最近研究NSGA2算法,网上有很多示例代码,但是基本没有...
  • 代码世界的Lambda

    千次阅读 2016-09-23 20:28:38
    Lambda表达式基于数学的λ演算得名,可以看作是匿名函数,可以代替表达式,函数,闭包等,也支持类型推论,可以远离匿名内部类。Lambda的目的是让程序员能够...Java、Python和大数据云计算的Lambda分别是什么样的?
  • Range对象详解

    千次阅读 2013-05-06 23:31:35
    Range对象详解 需求:很多时候,我们要通过js向文本输入框(textarea)里添加内容,这些内容有部分文字需要直接选择,然后方便用户修改。这时候就需要用到对象...通常,Range代表用户的一个选择区域。本文集中于获
  • C++11 | range-based for loop

    千次阅读 2016-06-06 13:13:01
    C++11里支持range-based for loop了,看看带来了哪些方便
  • html5之Range对象详解

    千次阅读 2017-08-10 17:35:02
    一:Range对象的概念 Range对象代表页面上一段连续的区域...在Js的document文档有一个方法用来创建一个Range对象,代码如下: var range = document.createRange(); 在html5,每一个浏览器窗口都会有一个select
  • antd-RangePicker

    千次阅读 2019-04-26 14:43:29
    antd 使用RangePicker ...下面是使用组件代码: 首先需要先引入组件所需的东西 import { DatePicker, } from ‘antd’; const {RangePicker} = DatePicker; const dateFormat = ‘YYYY/MM/DD HH:mm:ss’; import mom...
  • Range函数 Arange函数 用法:np.arange()函数分为一个参数,两个参数,三个参数三种情况,函数返回一个有终点和起点的固定步长的排列 实际代码: import numpy as np a = np.arange(6) b = np.arange(1,6) c = np.a...
  • 问题一:在VBA代码中,如何引用当前工作表的单个单元格(例如引用单元格C3)? 回答:可以使用下面列举的任一方式对当前工作表的单元格(C3)进行引用。 (1) Range("C3") (2) [C3] (3) Cells(3, 3) ...
  •  我们创建一个名叫WordTest的Windows窗体应用程序作为本次试验的工程,为了能够显示word的内容,我们先要在下图所示的Program.cs的main()函数引入控制台Console来显示读取到内容。   Program.cs的代码: n
  • 处理Selection对象和Range对象——Word VBA重要的两个对象Word 开发人员参考Selection 对象代表窗口或窗格的当前所选内容。所选内容代表文档选定(或突出显示)的区域,如果文档没有选定任何内容,则代表...
  • 利用切片(Slice)操作指定参数: L[parameter 1: parameter 2: parameter 3]:parameter 3表示每N个取一个 例如:L = range(0, 101) L[3::3],代表每3个数里取一个,取的数是这3个数的第一个数 这行代码的意思是...
  • range()的用法

    千次阅读 多人点赞 2020-04-28 14:38:01
    range()的用法 1、 函数range() 让Python从你指定的第一个值开始数,并在到达你指定的第二个值后停止,因此输出不包含第二个值(这里为5)。 for value in range(1,5): print(value) 执行结果为: 1 2 3 4 2、...
  • Excel的Range对象(C#)

    千次阅读 2015-11-18 09:50:47
    一个 Range 对象代表一个单元格、一行、一列、包含一个或者更多单元块(可以是连续的单元格,也可以式不连续的单元格)的选定单元格,甚至是多个工作表上的一组单元格。 在代码中引用范围。 Address 属性:
  • python的range()函数,list的slide用法

    千次阅读 2015-05-19 21:32:05
    1.range()用法代码>>> range(1,5) #代表从1到5(不包含5) [1, 2, 3, 4] >>> range(1,5,2) #代表从1到5,间隔2(不包含5) [1, 3] >>> range(5) #代表从0到5(不包含5) [0, 1, 2, 3, 4]2.list的slide用法array = [1, 2, 5...
  • HTTP之Range理解

    万次阅读 2015-06-12 17:28:45
    1、什么Range?  当用户在听一首歌的时候,如果听到一半(网络下载了一半),网络断掉了,用户需要继续听的时候,文件服务器不支持断点的话,则用户需要重新下载这个文件。而Range支持的话,客户端应该记录了...
  • Excel VBA:Range对象

    千次阅读 2020-01-13 15:28:50
    Range("A1:C5")或Range("A1","C5")表示A1:C5区域; Range(cells(i,j),cells(m,n))表示第i行第j列的单元格到第m行n列的单元格区域,有时cells前需加worksheets("sheet1").之类; 2.表示不连续区域 Range("A1:B2...
  • range 对象应用(上)

    千次阅读 2013-12-16 22:01:37
    range 对象 应用(上) .net技术 2010-09-16 12:01:53 阅读47 评论0  字号:大中小 订阅 毫无疑问,Range对象是Excel对象模型最重要的对象,几乎所有与工作表有关的实质性操作都涉及到Range...在VBA代码中
  • mysql分区之range分区

    千次阅读 2016-11-22 17:15:02
    随着互联网的发展,各方面的数据越来越多,从最近两年大数据越来越强的呼声就可见一斑。 我们所做的项目虽算不上什么大项目,但是由于业务量的问题,数据也是相当的多。 数据一多,就很容易出现性能问题,而为了...
  • Python range函数深入解析

    千次阅读 2010-12-17 16:56:00
    , stop[, step])start:开始值stop:结束值(连续区域可以认为是索引)step:步进(可以正负,正数代表自左向右移动,负数代表自又向左移动)源代码: 数据连续区域:print("view: " + str(list(range(3)))) ...
  • Selection和Range对象

    千次阅读 2015-09-30 18:07:44
     其实在对word操作的时候Range对象用到的是最多的, 又由于Range对象与 Selection对象共享许多相同的方法和属性,所以今天我们将他俩放到一起来介绍下,看看他们有哪些共同和不同的地方。 正题 1.处理 Selection...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 136,224
精华内容 54,489
关键字:

代码中range代表什么