十四伤溉、使用 Canvas 繪圖

??HTML5 添加的最受歡迎的功能就是<canvas>元素辫秧。這個元素負(fù)責(zé)在頁面中設(shè)定一個區(qū)域束倍,然后就可以通過 JavaScript 動態(tài)地在這個區(qū)域中繪制圖形。

??<canvas>元素最早是由蘋果公司推出的盟戏,當(dāng)時主要用在其 Dashboard 微件中绪妹。

??很快,HTML5 加入了這個元素柿究,主流瀏覽器也迅速開始支持它邮旷。IE9+、Firefox 1.5+蝇摸、Safari 2+婶肩、Opera 9+、Chrome貌夕、iOS 版 Safari 以及 Android 版 WebKit 都在某種程度上支持<canvas>狡孔。

??與瀏覽器環(huán)境中的其他組件類似,<canvas>由幾組 API 構(gòu)成蜂嗽,但并非所有瀏覽器都支持所有這些 API苗膝。除了具備基本繪圖能力的 2D 上下文,<canvas>還建議了一個名為 WebGL 的 3D 上下文植旧。

??目前辱揭,支持該元素的瀏覽器都支持 2D 上下文及文本 API,但對 WebGL 的支持還不夠好病附。由于 WebGL 還是實驗性的问窃,因此要得到所有瀏覽器支持還需要很長一段時間。Firefox 4+和 Chrome 支持 WebGL 規(guī)范的早期版本完沪,但一些老版本的操作系統(tǒng)域庇,比如 Windows XP,由于缺少必要的繪圖驅(qū)動程序覆积,即便安裝了這兩款瀏覽器也無濟(jì)于事听皿。

1、基本用法

??要使用 <canvas> 元素宽档,必須先設(shè)置 width 和 height 屬性尉姨,指定可以繪圖的區(qū)域大小。出現(xiàn)在開始和結(jié)束標(biāo)簽中的內(nèi)容是后備信息吗冤,如果瀏覽器不支持 <canvas> 元素又厉,就會顯示這些信息九府。下面就是 <canvas> 元素的例子。

<canvas id="drawing" width="200" height="200">A drawing of something.</canvas>

??與其它元素一樣覆致,<canvas> 元素對應(yīng)的 DOM 元素對象也有 width 和 height 屬性侄旬,可以隨意修改。而且煌妈,也能通過 CSS 為該元素添加樣式勾怒,如果不添加任何樣式或者不繪制任何圖形,在頁面中是看不到該元素的声旺。
??要在這塊畫布(canvas)上繪圖,需要取得繪圖上下文段只。而取得繪圖上下文對象的引用腮猖,需要調(diào)用 getContext() 方法并傳入上下文的名字。傳入"2d"赞枕,就可以取得 2D 上下文對象澈缺。

var drawing = document.getElementById('drawing');

// 確定瀏覽器支持<canvas>元素
if (drawing.getContext) {
    var context = drawing.getContext("2d");
    // 更多代碼
}

??在使用 <canvas> 元素之前,首先要檢測 getContext() 方法是否存在炕婶,這一步非常重要姐赡。有些瀏覽器會為 HTML 規(guī)范之外的元素創(chuàng)建默認(rèn)的 HTML 元素對象(假設(shè)你想在 Firefox3 中使用 <canvas> 元素,雖然瀏覽器會為該標(biāo)簽創(chuàng)建一個 DOM 對象柠掂,但這個對象中并沒有 getContext() 方法项滑。)。在這種情況下涯贞,即使 drawing 變量中保存著一個有效的元素引用枪狂,也檢測不到 getContext() 方法。
??使用 toDataURL() 方法宋渔,可以導(dǎo)出在<canvas>元素上繪制的圖像州疾。這個方法接受一個參數(shù),即圖像的 MIME 類型格式皇拣,而且適合用于創(chuàng)建圖像的任何上下文严蓖。比如,要取得畫布中的一幅 PNG 格式的圖像氧急,可以使用以下代碼颗胡。

var drawing = document.getElementById("drawing");

// 確定瀏覽器支持<canvas>元素
if (drawing.getContext()) {
    // 取得圖像的數(shù)據(jù) URI
    var imgURI = drawing.toDataURL("image/png");

    // 顯示圖像
    var image = document.creatElement("img");
    image.src = imgURI;
    document.body.appendChild(image);
}

??默認(rèn)情況下,瀏覽器會將圖像編碼為 PNG 格式(除非另行指定)吩坝。Firefox 和 Opera 也支持基于 "image/jpeg" 參數(shù)的 JPEG 編碼格式杭措。由于這個方法是后來才追加的,所以支持<canvas>的瀏覽器也是在較新的版本中才加入了對它的支持钾恢,比如 IE9手素、Firefox3.5 和 Opera10鸳址。

如果繪制到畫布上的圖像源自不同的域犯戏,toDataURL() 方法會拋出錯誤与殃。

2擦酌、2D 上下文

??使用 2D 繪圖上下文提供的方法部蛇,可以繪制簡單的 2D 圖形瓢谢,比如矩形镐作、弧線和路徑廊鸥。
??2D 上下文的坐標(biāo)開始于<canvas>元素的左上角邦投,原點坐標(biāo)是(0,0)邓嘹。所有坐標(biāo)值都基于這個原點計算酣栈,x 值越大表示越靠右,y 值越大表示越靠下汹押。默認(rèn)情況下矿筝,width 和 height 表示水平和垂直兩個方向上可用的像素數(shù)目。

2.1棚贾、 填充和描邊

??2D 上下文的兩種基本繪圖操作是填充和描邊窖维。

??填充,就是用指定的樣式(顏色妙痹、漸變或圖像)填充圖形铸史;

??描邊,就是只在圖形的邊緣畫線怯伊。

??大多數(shù) 2D 上下文操作都會細(xì)分為填充和描邊兩個操作琳轿,而操作的結(jié)果取決于兩個屬性:fillStyle 和 strokeStyle。這兩個屬性的值可以是字符串耿芹、漸變對象或模式對象利赋,而且它們的默認(rèn)值是"#000000"。
??如果為它們指定表示顏色的字符串值猩系,可以使用 CSS 中指定顏色值的任何格式媚送,包括顏色名、十六進(jìn)制碼寇甸、rgb塘偎、rgba、hsl 或 hsla拿霉。示例:

var drawing = document.getElementById("drawing");

// 確定瀏覽器支持<canvas>元素
if (drawing.getContext){
    var context = drawing.getContext("2d");
    context.strokeStyle = "red";
    context.fillStyle = "#0000ff";
}

??以上代碼將 strokeStyle 設(shè)置為 red(CSS 中的顏色名)吟秩,將 fillStyle 設(shè)置為#0000ff(藍(lán)色)。
??然后绽淘,所有涉及描邊和填充的操作都將使用這兩個樣式涵防,直至重新設(shè)置這兩個值。如前所述沪铭,這兩個屬性的值也可以是漸變對象或模式對象壮池。本章后面會討論這兩種對象偏瓤。

2.2、 繪制矩形

??矩形是唯一一種可以直接在 2D 上下文中繪制的形狀椰憋。與矩形有關(guān)的方法包括 fillRect()、strokeRect() 和 clearRect()橙依。
??這三個方法都能接收 4 個參數(shù):矩形的 x 坐標(biāo)证舟、矩形的 y 坐標(biāo)、矩形寬度和矩形高度窗骑。這些參數(shù)的單位都是像素女责。

??首先,fillRect() 方法在畫布上繪制的矩形會填充指定的顏色创译。填充的顏色通過 fillStyle 屬性指定抵知,比如:

var drawing = document.getElementById("drawing");

// 確定瀏覽器支持<canvas>元素
if (drawing.getContext){
    var context = drawing.getContext("2d");

    // 繪制紅色矩形
    context.fillStyle = "#ff0000";
    context.fillRect(10, 10, 50, 50);

    // 繪制半透明的藍(lán)色矩形
    context.fillStyle = "rgba(0, 0, 255, .5)";
    context.fillRect(30, 30, 50, 50);
}

??以上代碼首先將 fillStyle 設(shè)置為紅色,然后從(10, 10)處開始繪制矩形昔榴,矩形的寬和高均為 50 像素。然后碘橘,通過 rgba() 格式再將 fillStyle 設(shè)置為半透明的藍(lán)色互订,在第一個矩形上面繪制第二個矩形。結(jié)果就是可以透過藍(lán)色的矩形看到紅色的矩形痘拆。

??strokeRect() 方法在畫布上繪制的矩形會使用指定的顏色描邊仰禽。描邊顏色通過 strokeStyle 屬性指定。示例:

var drawing = document.getElementById("drawing");

// 確定瀏覽器支持<canvas>元素
if (drawing.getContext){
    var context = drawing.getContext("2d");

    // 繪制紅色描邊矩形
    context.strokeStyle= "#ff0000";
    context.strokeRect(10, 10, 50, 50);

    // 繪制半透明的藍(lán)色描邊矩形
    context.strokeStyle= "rgba(0, 0, 255, .5)";
    context.strokeRect(30, 30, 50, 50);
}

??以上代碼繪制了兩個重疊的矩形纺蛆。不過吐葵,這兩個矩形都只有框線,內(nèi)部并沒有填充顏色桥氏。

??描邊線條的寬度由 lineWidth 屬性控制温峭,該屬性的值可以是任意整數(shù)。
??另外字支,通過 lineCap 屬性可以控制線條末端的形狀是平頭凤藏、圓頭還是方頭("butt"、"round" 或 "square")堕伪。
??通過 lineJoin 屬性可以控制線條相交的方式是圓交揖庄、斜
交還是斜接("round"、"bevel" 或 "miter")欠雌。

??最后蹄梢,clearRect() 方法用于清除畫布上的矩形區(qū)域。本質(zhì)上富俄,這個方法可以把繪制上下文中的某一矩形區(qū)域變透明禁炒。通過繪制形狀然后再清除指定區(qū)域而咆,就可以生成有意思的效果,例如把某個形狀切掉一塊齐苛。示例:

var drawing = document.getElementById("drawing");

// 確定瀏覽器支持<canvas>元素
if (drawing.getContext){
    var context = drawing.getContext("2d");

    // 繪制紅色矩形
    context.fillStyle = "#ff0000";
    context.fillRect(10, 10, 50, 50);

    // 繪制半透明的藍(lán)色矩形
    context.fillStyle = "rgba(0, 0, 255, .5)";
    context.fillRect(30, 30, 50, 50);

   // 在兩個矩形重疊的地方清除一個小矩形
    context.clearRect(40, 40, 10, 10); 
}

2.3翘盖、繪制路徑

??2D 繪制上下文支持很多在畫布上繪制路徑的方法。通過路徑可以創(chuàng)造出復(fù)雜的形狀和線條凹蜂。

??要繪制路徑馍驯,首先必須調(diào)用 beginPath() 方法,表示要開始繪制新路徑玛痊。然后汰瘫,再通過調(diào)用下列方法來實際地繪制路徑。

  • arc(x, y, radius, startAngle, endAngle, counterclockwise):以(x, y)為圓心繪制一條弧線擂煞,弧線半徑為 radius混弥,起始和結(jié)束角度(用弧度表示)分別為 startAngle 和 endAngle。最后一個參數(shù)表示 startAngle 和 endAngle 是否按逆時針方向計算对省,值為 false 表示按順時針方向計算蝗拿。
  • arcTo(x1, y1, x2, y2, radius):從上一點開始繪制一條弧線,到(x2, y2)為止蒿涎,并且以給定的半徑 radius 穿過(x1, y1)哀托。
  • bezierCurveTo(c1x, c1y, c2x, c2y, x, y):從上一點開始繪制一條曲線,到(x, y) 為止劳秋,并且以 (c1x, c1y) 和 (c2x, c2y) 為控制點仓手。
  • lineTo(x, y):從上一點開始繪制一條直線,到(x, y)為止玻淑。
  • moveTo(x, y):將繪圖游標(biāo)移動到(x, y)嗽冒,不畫線。
  • quadraticCurveTo(cx, cy, x, y):從上一點開始繪制一條二次曲線补履,到(x, y)為止添坊,并且以(cx, cy)作為控制點。
  • rect(x, y, width, height):從點(x, y)開始繪制一個矩形箫锤,寬度和高度分別由 width 和 height 指定帅腌。這個方法繪制的是矩形路徑,而不是 strokeRect() 和 fillRect() 所繪制的獨立的形狀麻汰。

??創(chuàng)建了路徑后速客,接下來有幾種可能的選擇。如果想繪制一條連接到路徑起點的線條五鲫,可以調(diào)用 closePath()溺职。

??如果路徑已經(jīng)完成,你想用 fillStyle 填充它,可以調(diào)用 fill() 方法浪耘。

??另外乱灵,還可以調(diào)用 stroke() 方法對路徑描邊,描邊使用的是 strokeStyle七冲。

??最后還可以調(diào)用 clip()痛倚,這個方法可以在路徑上創(chuàng)建一個剪切區(qū)域。

??下面看一個例子澜躺,即繪制一個不帶數(shù)字的時鐘表盤蝉稳。

var drawing = document.getElementById("drawing");

// 確定瀏覽器支持<canvas>元素
if (drawing.getContext){

    var context = drawing.getContext("2d");

    // 開始路徑
    context.beginPath();

    // 繪制外圓
    context.arc(100, 100, 99, 0, 2 * Math.PI, false);

    // 繪制內(nèi)圓
    context.moveTo(194, 100);
    context.arc(100, 100, 94, 0, 2 * Math.PI, false);

    // 繪制分針
    context.moveTo(100, 100);
    context.lineTo(100, 15);

    // 繪制時針
    context.moveTo(100, 100);
    context.lineTo(35, 100);

    // 描邊路徑
    context.stroke();
}

??上述例子使用 arc() 方法繪制了兩個圓形:一個外圓和一個內(nèi)圓,構(gòu)成了表盤的邊框掘鄙。外圓的半徑是 99 像素耘戚,圓心位于點(100,100),也是畫布的中心點操漠。為了繪制一個完整的圓形收津,我們從 0 弧度開始,繪制 2π 弧度(通過 Math.PI 來計算)浊伙。
??在繪制內(nèi)圓之前撞秋,必須把路徑移動到內(nèi)圓上的某一點,以避免繪制出多余的線條嚣鄙。
??第二次調(diào)用 arc() 使用了小一點的半徑吻贿,以便創(chuàng)造邊框的效果。
??然后拗慨,組合使用 moveTo() 和 lineTo() 方法來繪制時針和分針廓八。
??最后一步是調(diào)用 stroke() 方法奉芦,這樣才能把圖形繪制到畫布上赵抢,如下圖所示:

??在 2D 繪圖上下文中,路徑是一種主要的繪圖方式声功,因為路徑能為要繪制的圖形提供更多控制烦却。
??由于路徑的使用很頻繁,所以就有了一個名為 isPointInPath() 的方法先巴。這個方法接收 x 和 y 坐標(biāo)作為參數(shù)其爵,用于在路徑被關(guān)閉之前確定畫布上的某一點是否位于路徑上,例如:

if (context.isPointInPath(100, 100)){
    alert("Point (100, 100) is in the path.");
}

??2D 上下文中的路徑 API 已經(jīng)非常穩(wěn)定伸蚯,可以利用它們結(jié)合不同的填充和描邊樣式摩渺,繪制出非常復(fù)雜的圖形來。

2.4剂邮、 繪制文本

??文本與圖形總是如影隨形摇幻。為此,2D 繪圖上下文也提供了繪制文本的方法。

??繪制文本主要有兩個方法:fillText() 和 strokeText()绰姻。這兩個方法都可以接收 4 個參數(shù):要繪制的文本字符串枉侧、x 坐標(biāo)、y 坐標(biāo)和可選的最大像素寬度狂芋。而且榨馁,這兩個方法都以下列 3 個屬性為基礎(chǔ)。

  • font:表示文本樣式帜矾、大小及字體翼虫,用 CSS 中指定字體的格式來指定,例如"10px Arial"黍特。
  • textAlign:表示文本對齊方式蛙讥。可能的值有 "start"灭衷、"end"次慢、"left"、"right" 和 "center"翔曲。建議使用"start"和"end"迫像,不要使用"left"和"right",因為前兩者的意思更穩(wěn)妥瞳遍,能同時適合從左到右和從右到左顯示(閱讀)的語言闻妓。
  • textBaseline:表示文本的基線÷有担可能的值有 "top"由缆、"hanging"、"middle"猾蒂、 "alphabetic"均唉、"ideographic" 和 "bottom"。

??這幾個屬性都有默認(rèn)值肚菠,因此沒有必要每次使用它們都重新設(shè)置一遍值舔箭。

??fillText() 方法使用 fillStyle 屬性繪制文本,而 strokeText() 方法使用 strokeStyle 屬性為文本描邊蚊逢。
??相對來說层扶,還是使用 fillText() 的時候更多,因為該方法模仿了在網(wǎng)頁中正常顯示文本烙荷。例如镜会,下面的代碼在前一節(jié)創(chuàng)建的表盤上方繪制了數(shù)字 12:

context.font = "bold 14px Arial";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillText("12", 100, 20); 

??因為這里把 textAlign 設(shè)置為"center",把 textBaseline 設(shè)置為"middle"终抽,所以坐標(biāo)(100, 20) 表示的是文本水平和垂直中點的坐標(biāo)戳表。
??如果將 textAlign 設(shè)置為"start"焰薄,則 x 坐標(biāo)表示的是文本左端的位置(從左到右閱讀的語言);設(shè)置為"end"扒袖,則 x 坐標(biāo)表示的是文本右端的位置(從左到右閱讀的
語言)塞茅。例如:

// 正常
context.font = "bold 14px Arial";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillText("12", 100, 20);

// 起點對齊
context.textAlign = "start";
context.fillText("12", 100, 40);

// 終點對齊
context.textAlign = "end";
context.fillText("12", 100, 60);

??這一回繪制了三個字符串"12",每個字符串的 x 坐標(biāo)值相同季率,但 textAlign 值不同野瘦。另外,后兩個字符串的 y 坐標(biāo)依次增大飒泻,以避免相互重疊鞭光。結(jié)果如下圖所示:

??表盤中的分針恰好位于正中間,因此文本的水平對齊方式如何變化也能夠一目了然泞遗。

??類似地惰许,修改 textBaseline 屬性的值可以調(diào)整文本的垂直對齊方式:值為"top",y 坐標(biāo)表示文本頂端史辙;值為 "bottom"汹买,y 坐標(biāo)表示文本底端;值為 "hanging"聊倔、"alphabetic" 和 "ideographic"晦毙,則 y 坐標(biāo)分別指向字體的特定基線坐標(biāo)。

??由于繪制文本比較復(fù)雜耙蔑,特別是需要把文本控制在某一區(qū)域中的時候见妒,2D 上下文提供了輔助確定文本大小的方法 measureText()。
??這個方法接收一個參數(shù)甸陌,即要繪制的文本须揣;返回一個 TextMetrics 對象。返回的對象目前只有一個 width 屬性钱豁,但將來還會增加更多度量屬性耻卡。

??measureText() 方法利用 font、textAlign 和 textBaseline 的當(dāng)前值計算指定文本的大小寥院。
??比如劲赠,假設(shè)你想在一個 140 像素寬的矩形區(qū)域中繪制文本 Hello world!涛目,下面的代碼從 100 像素的字體大小開始遞減秸谢,最終會找到合適的字體大小。

var fontSize = 100;
context.font = fontSize + "px Arial";
while(context.measureText("Hello world!").width > 140){
    fontSize--;
    context.font = fontSize + "px Arial";
}
context.fillText("Hello world!", 10, 10);
context.fillText("Font size is " + fontSize + "px", 10, 50); 

??前面提到過霹肝,fillText 和 strokeText() 方法都可以接收第四個參數(shù)估蹄,也就是文本的最大像素寬度。不過沫换,這個可選的參數(shù)尚未得到所有瀏覽器支持(最早支持它的是 Firefox 4)臭蚁。
??提供這個參數(shù)后,調(diào)用 fillText() 或 strokeText() 時如果傳入的字符串大于最大寬度,則繪制的文本字符的高度正確垮兑,但寬度會收縮以適應(yīng)最大寬度冷尉。
??繪制文本還是相對比較復(fù)雜的操作,因此支持<canvas>元素的瀏覽器也并未完全實現(xiàn)所有與繪制文本相關(guān)的 API系枪。

2.5雀哨、變換

??通過上下文的變換,可以把處理后的圖像繪制到畫布上私爷。2D 繪制上下文支持各種基本的繪制變換雾棺。
??創(chuàng)建繪制上下文時,會以默認(rèn)值初始化變換矩陣衬浑,在默認(rèn)的變換矩陣下捌浩,所有處理都按描述直接繪制。
??為繪制上下文應(yīng)用變換工秩,會導(dǎo)致使用不同的變換矩陣應(yīng)用處理尸饺,從而產(chǎn)生不同的結(jié)果。
??可以通過如下方法來修改變換矩陣助币。

  • rotate(angle):圍繞原點旋轉(zhuǎn)圖像 angle 弧度侵佃。
  • scale(scaleX, scaleY):縮放圖像,在 x 方向乘以 scaleX奠支,在 y 方向乘以 scaleY馋辈。scaleX 和 scaleY 的默認(rèn)值都是 1.0。
  • translate(x, y):將坐標(biāo)原點移動到(x,y)倍谜。執(zhí)行這個變換之后迈螟,坐標(biāo)(0,0)會變成之前由(x, y)表示的點。
  • transform(m1_1, m1_2, m2_1, m2_2, dx, dy):直接修改變換矩陣,方式是乘以如下矩陣载弄。
    m1_1??m1_2??dx
    m2_1??m2_2??dy
    0????0????1
  • setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy):將變換矩陣重置為默認(rèn)狀態(tài)嘉涌,然后再調(diào)用 transform()。

??變換有可能很簡單损敷,但也可能很復(fù)雜,這都要視情況而定堤尾。比如,就拿前面例子中繪制表針來說舱殿,如果把原點變換到表盤的中心刺彩,然后再繪制表針就容易多了迷郑。請看下面的例子枝恋。

var drawing = document.getElementById("drawing");

// 確定瀏覽器支持<canvas>元素
if (drawing.getContext){ 

    var context = drawing.getContext("2d");

    // 開始路徑
    context.beginPath();

    // 繪制外圓
    context.arc(100, 100, 99, 0, 2 * Math.PI, false);

    // 繪制內(nèi)圓
    context.moveTo(194, 100);
    context.arc(100, 100, 94, 0, 2 * Math.PI, false);

    // 變換原點
    context.translate(100, 100);

    // 繪制分針
    context.moveTo(0,0);
    context.lineTo(0, -85);

    // 繪制時針
    context.moveTo(0, 0);
    context.lineTo(-65, 0);

    // 描邊路徑
    context.stroke();
}

??把原點變換到時鐘表盤的中心點(100, 100)后,在同一方向上繪制線條就變成了簡單的數(shù)學(xué)問題了嗡害。所有數(shù)學(xué)計算都基于(0, 0)焚碌,而不是(100, 100)。
??還可以更進(jìn)一步霸妹,像下面這樣使用 rotate() 方法旋轉(zhuǎn)時鐘的表針十电。

var drawing = document.getElementById("drawing");

// 確定瀏覽器支持<canvas>元素
if (drawing.getContext){ 

    var context = drawing.getContext("2d");

    // 開始路徑
    context.beginPath();

    // 繪制外圓
    context.arc(100, 100, 99, 0, 2 * Math.PI, false);

    // 繪制內(nèi)圓
    context.moveTo(194, 100);
    context.arc(100, 100, 94, 0, 2 * Math.PI, false);

    // 變換原點
    context.translate(100, 100);

     // 旋轉(zhuǎn)表針
     context.rotate(1);

    // 繪制分針
    context.moveTo(0,0);
    context.lineTo(0, -85);

    // 繪制時針
    context.moveTo(0, 0);
    context.lineTo(-65, 0);

    // 描邊路徑
    context.stroke();
}

??因為原點已經(jīng)變換到了時鐘表盤的中心點,所以旋轉(zhuǎn)也是以該點為圓心的叹螟。結(jié)果就像是表針真地被固定在表盤中心一樣鹃骂,然后向右旋轉(zhuǎn)了一定角度。

??無論是剛才執(zhí)行的變換罢绽,還是 fillStyle畏线、strokeStyle 等屬性,都會在當(dāng)前上下文中一直有效良价,除非再對上下文進(jìn)行什么修改寝殴。
??雖然沒有什么辦法把上下文中的一切都重置回默認(rèn)值,但有兩個方法可以跟蹤上下文的狀態(tài)變化明垢。如果你知道將來還要返回某組屬性與變換的組合蚣常,可以調(diào)用 save() 方法。
??調(diào)用這個方法后痊银,當(dāng)時的所有設(shè)置都會進(jìn)入一個棧結(jié)構(gòu)抵蚊,得以妥善保管。然后可以對上下文進(jìn)行其他修改溯革。等想要回到之前保存的設(shè)置時泌射,可以調(diào)用 restore() 方法,在保存設(shè)置的棧結(jié)構(gòu)中向前返回一級鬓照,
恢復(fù)之前的狀態(tài)熔酷。
??連續(xù)調(diào)用 save() 可以把更多設(shè)置保存到棧結(jié)構(gòu)中,之后再連續(xù)調(diào)用 restore() 則可以一級一級返回豺裆。下面來看一個例子拒秘。

context.fillStyle = "#ff0000";
context.save();

context.fillStyle = "#00ff00";
context.translate(100, 100);
context.save();

context.fillStyle = "#0000ff";
context.fillRect(0, 0, 100, 200); // 從點(100,100)開始繪制藍(lán)色矩形

context.restore();
context.fillRect(10, 10, 100, 200); // 從點(110,110)開始繪制綠色矩形

context.restore(); 
context.fillRect(0, 0, 100, 200); // 從點(0,0)開始繪制紅色矩形

??首先,將 fillStyle 設(shè)置為紅色臭猜,并調(diào)用 save() 保存上下文狀態(tài)躺酒。
??接下來,把 fillStyle 修改為綠色蔑歌,把坐標(biāo)原點變換到(100, 100)羹应,再調(diào)用 save() 保存上下文狀態(tài)。
??然后次屠,把 fillStyle 修改為藍(lán)色并繪制藍(lán)色的矩形园匹。因為此時的坐標(biāo)原點已經(jīng)變了雳刺,所以矩形的左上角坐標(biāo)實際上是(100, 100)。
??然后調(diào)用 restore()裸违,之后 fillStyle 變回了綠色掖桦,因而第二個矩形就是綠色。之所以第二個矩形的起點坐標(biāo)是(110, 110)供汛,是因為坐標(biāo)位置的變換仍然起作用枪汪。
??再調(diào)用一次 restore(),變換就被取消了怔昨,而 fillStyle 也返回了紅色雀久。所以最后一個矩形是紅色的,而且繪制的起點是(0,0)趁舀。
??需要注意的是岸啡,save() 方法保存的只是對繪圖上下文的設(shè)置和變換,不會保存繪圖上下文的內(nèi)容赫编。

2.6巡蘸、 繪制圖像

?? 繪圖上下文內(nèi)置了對圖像的支持。如果你想把一幅圖像繪制到畫布上擂送,可以使用 drawImage() 方法悦荒。根據(jù)期望的最終結(jié)果不同,調(diào)用這個方法時嘹吨,可以使用三種不同的參數(shù)組合搬味。最簡單的調(diào)用方式是傳入一個 HTML <img> 元素,以及繪制該圖像的起點的 x 和 y 坐標(biāo)蟀拷。例如:

var image = document.images[0];
context.drawImage(image, 10, 10);

??這兩行代碼取得了文檔中的第一幅圖像碰纬,然后將它繪制到上下文中,起點為(10, 10)问芬。繪制到畫布上的圖像大小與原始大小一樣悦析。
??如果你想改變繪制后圖像的大小,可以再多傳入兩個參數(shù)此衅,分別表示目標(biāo)寬度和目標(biāo)高度强戴。通過這種方式來縮放圖像并不影響上下文的變換矩陣。例如:

context.drawImage(image, 50, 10, 20, 30);

??執(zhí)行代碼后挡鞍,繪制出來的圖像大小會變成 20×30 像素骑歹。

??除了上述兩種方式,還可以選擇把圖像中的某個區(qū)域繪制到上下文中墨微。

??drawImage() 方法的這種調(diào)用方式總共需要傳入 9 個參數(shù):要繪制的圖像道媚、源圖像的 x 坐標(biāo)、源圖像的 y 坐標(biāo)、源圖像的寬度最域、源圖像的高度谴分、目標(biāo)圖像的 x 坐標(biāo)、目標(biāo)圖像的 y 坐標(biāo)羡宙、目標(biāo)圖像的寬度狸剃、目標(biāo)圖像的高度掐隐。這樣調(diào)用 drawImage() 方法可以獲得最多的控制狗热。例如:

context.drawImage(image, 0, 10, 50, 50, 0, 100, 40, 60); 

??這行代碼只會把原始圖像的一部分繪制到畫布上。原始圖像的這一部分的起點為(0, 10)虑省,寬和高都是 50 像素匿刮。最終繪制到上下文中的圖像的起點是(0, 100),而大小變成了 40×60 像素探颈。
??這種調(diào)用方式可以創(chuàng)造出很有意思的效果熟丸,如下圖所示:

??除了給 drawImage() 方法傳入 HTML <img> 元素外,還可以傳入另一個<canvas>元素作為其第一個參數(shù)伪节。這樣光羞,就可以把另一個畫布內(nèi)容繪制到當(dāng)前畫布上。
??結(jié)合使用 drawImage() 和其他方法怀大,可以對圖像進(jìn)行各種基本操作纱兑。而操作的結(jié)果可以通過 toDataURL() 方法獲得。(請讀者注意化借,雖然本章至今一直在討論 2D 繪圖上下文潜慎,但 toDataURL()是 Canvas 對象的方法,不是上下文對象的方法蓖康。)
??不過铐炫,有一個例外,即圖像不能來自其他域蒜焊。如果圖像來自其他域倒信,調(diào)用 toDataURL() 會拋出一個錯誤。打個比方泳梆,假如位于www.example.com 上的頁面繪制的圖像來自于 www.wrox.com堤结,那當(dāng)前上下文就會被認(rèn)為“不干凈”,因而會拋出錯誤鸭丛。

2.7竞穷、陰影

??2D 上下文會根據(jù)以下幾個屬性的值,自動為形狀或路徑繪制出陰影鳞溉。

  • shadowColor:用 CSS 顏色格式表示的陰影顏色瘾带,默認(rèn)為黑色。
  • shadowOffsetX:形狀或路徑 x 軸方向的陰影偏移量熟菲,默認(rèn)為 0看政。
  • shadowOffsetY:形狀或路徑 y 軸方向的陰影偏移量朴恳,默認(rèn)為 0。
  • shadowBlur:模糊的像素數(shù)允蚣,默認(rèn) 0于颖,即不模糊。

??這些屬性都可以通過 context 對象來修改嚷兔。只要在繪制前為它們設(shè)置適當(dāng)?shù)闹瞪ィ湍茏詣赢a(chǎn)生陰影。例如:

var context = drawing.getContext("2d");

// 設(shè)置陰影
context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.shadowBlur = 4;
context.shadowColor = "rgba(0, 0, 0, 0.5)";

// 繪制紅色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);

// 繪制藍(lán)色矩形
context.fillStyle = "rgba(0,0,255,1)"; 
context.fillRect(30, 30, 50, 50); 

??兩個矩形的陰影樣式相同冒晰,如下圖所示:

??不同瀏覽器對陰影的支持有一些差異同衣。IE9、Firefox 4 和 Opera 11 的行為最為規(guī)范壶运,其他瀏覽器多多少少會有一些奇怪的現(xiàn)象耐齐,甚至根本不支持陰影。
??Chrome(直至第 10 版)不能正確地為描邊的形狀應(yīng)用實心陰影蒋情。Chrome 和 Safari(直至第 5 版)在為帶透明像素的圖像應(yīng)用陰影時也會有問題:不透明部分的下方本來是該有陰影的埠况,但此時則一概不見了。Safari 也不能給漸變圖形應(yīng)用陰影棵癣,其他瀏覽器都可以辕翰。

2.8、漸變

??漸變由 CanvasGradient 實例表示浙巫,很容易通過 2D 上下文來創(chuàng)建和修改金蜀。要創(chuàng)建一個新的線性漸變,可以調(diào)用 createLinearGradient() 方法的畴。這個方法接收 4 個參數(shù):起點的 x 坐標(biāo)渊抄、起點的 y 坐標(biāo)、終點的 x 坐標(biāo)丧裁、終點的 y 坐標(biāo)护桦。
??調(diào)用這個方法后,它就會創(chuàng)建一個指定大小的漸變煎娇,并返回 CanvasGradient 對象的實例二庵。
??創(chuàng)建了漸變對象后,下一步就是使用 addColorStop() 方法來指定色標(biāo)缓呛。這個方法接收兩個參數(shù):色標(biāo)位置和 CSS 顏色值催享。色標(biāo)位置是一個 0(開始的顏色)到 1(結(jié)束的顏色)之間的數(shù)字。例如:

var gradient = context.createLinearGradient(30, 30, 70, 70);
gradient.addColorStop(0, "white");
gradient.addColorStop(1, "black");

??此時哟绊,gradient 對象表示的是一個從畫布上點(30, 30)到點(70, 70)的漸變因妙。起點的色標(biāo)是白色,終點的色標(biāo)是黑色。然后就可以把 fillStyle 或 strokeStyle 設(shè)置為這個對象攀涵,從而使用漸變來繪制形狀或描邊:

// 繪制紅色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);

// 繪制漸變矩形
context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50);

??為了讓漸變覆蓋整個矩形铣耘,而不是僅應(yīng)用到矩形的一部分,矩形和漸變對象的坐標(biāo)必須匹配才行以故。以上代碼會得到如下圖所示的結(jié)果:

??如果沒有把矩形繪制到恰當(dāng)?shù)奈恢梦舷福强赡芫椭粫@示部分漸變效果。例如:

context.fillStyle = gradient; 
context.fillRect(50, 50, 50, 50);

??這兩行代碼執(zhí)行后得到的矩形只有左上角稍微有一點白色怒详。這主要是因為矩形的起點位于漸變的中間位置炉媒,而此時漸變差不多已經(jīng)結(jié)束了。由于漸變不重復(fù)棘利,所以矩形的大部分區(qū)域都是黑色橱野。如下圖所示:


??確保漸變與形狀對齊非常重要朽缴,有時候可以考慮使用函數(shù)來確保坐標(biāo)合適善玫。例如:

function createRectLinearGradient(context, x, y, width, height){
    return context.createLinearGradient(x, y, x + width, y + height);
}

??這個函數(shù)基于起點的 x 和 y 坐標(biāo)以及寬度和高度值來創(chuàng)建漸變對象,從而讓我們可以在 fillRect() 中使用相同的值密强。

var gradient = createRectLinearGradient(context, 30, 30, 50, 50);

gradient.addColorStop(0, "white");
gradient.addColorStop(1, "black");

//繪制漸變矩形
context.fi llStyle = gradient;
context.fillRect(30, 30, 50, 50);

??使用畫布的時候茅郎,確保坐標(biāo)匹配很重要,也需要一些技巧或渤。類似 createRectLinearGradient() 這樣的輔助方法可以讓控制坐標(biāo)更容易一些系冗。

??要創(chuàng)建徑向漸變(或放射漸變),可以使用 createRadialGradient() 方法薪鹦。這個方法接收 6 個參數(shù)掌敬,對應(yīng)著兩個圓的圓心和半徑。前三個參數(shù)指定的是起點圓的原心(x 和 y)及半徑池磁,后三個參數(shù)指定的是終點圓的原心(x 和 y)及半徑奔害。
??可以把徑向漸變想象成一個長圓桶,而這 6 個參數(shù)定義的正是這個桶的兩個圓形開口的位置地熄。如果把一個圓形開口定義得比另一個小一些华临,那這個圓桶就變成了圓錐體,而通過移動每個圓形開口的位置端考,就可達(dá)到像旋轉(zhuǎn)這個圓錐體一樣的效果雅潭。
??如果想從某個形狀的中心點開始創(chuàng)建一個向外擴(kuò)散的徑向漸變效果,就要將兩個圓定義為同心圓却特。
??比如扶供,就拿前面創(chuàng)建的矩形來說,徑向漸變的兩個圓的圓心都應(yīng)該在(55, 55)裂明,因為矩形的區(qū)域是從(30, 30)到(80,80)椿浓。請看代碼:

var gradient = context.createRadialGradient(55, 55, 10, 55, 55, 30);

gradient.addColorStop(0, "white");
gradient.addColorStop(1, "black");

//繪制紅色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);

//繪制漸變矩形
context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50); 

??運行代碼,會得到如下圖所示的結(jié)果。

??因為創(chuàng)建比較麻煩轰绵,所以徑向漸變并不那么容易控制粉寞。不過,一般來說左腔,讓起點圓和終點圓保持為同心圓的情況比較多唧垦,這時候只要考慮給兩個圓設(shè)置不同的半徑就好了。

2.9液样、模式

??模式其實就是重復(fù)的圖像振亮,可以用來填充或描邊圖形。要創(chuàng)建一個新模式鞭莽,可以調(diào)用 createPattern() 方法并傳入兩個參數(shù):一個 HTML <img> 元素和一個表示如何重復(fù)圖像的字符串坊秸。
??其中,第二個參數(shù)的值與 CSS 的background-repeat 屬性值相同澎怒,包括"repeat"褒搔、"repeat-x"、"repeat-y" 和 "no-repeat"。示例:

var image = document.images[0],
    pattern = context.createPattern(image, "repeat");

// 繪制矩形
context.fillStyle = pattern;
context.fillRect(10, 10, 150, 150); 

??需要注意的是,模式與漸變一樣逆巍,都是從畫布的原點(0, 0)開始的。
??將填充樣式(fillStyle)設(shè)置為模式對象琳状,只表示在某個特定的區(qū)域內(nèi)顯示重復(fù)的圖像,而不是要從某個位置開始繪制重復(fù)的圖像盒齿。
??createPattern() 方法的第一個參數(shù)也可以是一個<video>元素念逞,或者另一個<canvas>元素。

2.10边翁、使用圖像數(shù)據(jù)

??2D 上下文的一個明顯的長處就是翎承,可以通過 getImageData() 取得原始圖像數(shù)據(jù)。
??這個方法接收 4 個參數(shù):要取得其數(shù)據(jù)的畫面區(qū)域的 x 和 y 坐標(biāo)以及該區(qū)域的像素寬度和高度倒彰。
??示例审洞,要取得左上角坐標(biāo)為(10, 5)、大小為 50x50 像素的區(qū)域的圖像數(shù)據(jù)待讳,可以使用以下代碼:

var imageData = context.getImageData(10, 5, 50, 50);

??上述返回的對象是 ImageData 的實例芒澜。
??每個 ImageData 對象都有三個屬性:width、height 和 data创淡。
??其中 data 屬性是一個數(shù)組痴晦,保存著圖像中每一個像素的數(shù)據(jù)。在 data 數(shù)組中琳彩,每一個像素用 4 個元素來保存誊酌,分別表示紅部凑、綠、藍(lán)和透明度碧浊。因此涂邀,第一個像素的數(shù)據(jù)就保存在數(shù)組的第 0 到第 3 個元素中,示例:

var data = imageData,
    red = data[0],
    green = data[1],
    blue = data[2],
    alpha = data[3];

??數(shù)組中的每個元素的值都介于 0 到 255 之間(包括 0 和 255)箱锐。能夠直接訪問到原始圖像數(shù)據(jù)比勉,就能夠以各種方式來操作這些數(shù)據(jù)。
??例如驹止,通過修改圖像數(shù)據(jù)浩聋,可以像下面這樣創(chuàng)建一個簡單的灰階過濾器。

var drawing = document.getElementById("drawing");

// 確定瀏覽器支持<canvas>元素
if (drawing.getContext){
    var context = drawing.getContext("2d"),
        image = document.images[0],
        imageData, data,
        i, len, average,
        red, green, blue, alpha;

    // 繪制原始圖像
    context.drawImage(image, 0, 0);

    // 取得圖像數(shù)據(jù)
    imageData = context.getImageData(0, 0, image.width, image.height);
    data = imageData.data;

    for (i=0, len=data.length; i < len; i+=4){
        red = data[i];
        green = data[i+1];
        blue = data[i+2];
        alpha = data[i+3];

        // 求得 rgb 平均值
        average = Math.floor((red + green + blue) / 3);

        // 設(shè)置顏色值臊恋,透明度不變
        data[i] = average;
        data[i+1] = average;
        data[i+2] = average;
    }

    // 回寫圖像數(shù)據(jù)并顯示結(jié)果
    imageData.data = data;
    context.putImageData(imageData, 0, 0);
} 

??上述例子首先在畫面上繪制了一幅圖像衣洁,然后取得了原始圖像數(shù)據(jù)。其中的 for 循環(huán)遍歷了圖像數(shù)據(jù)中的每一個像素抖仅。這里要注意的是坊夫,每次循環(huán)控制變量 i 都遞增 4。
??在取得每個像素的紅岸售、綠践樱、藍(lán)顏色值后厂画,計算出它們的平均值凸丸。再把這個平均值設(shè)置為每個顏色的值,結(jié)果就是去掉了每個像素的顏色袱院,只保留了亮度接近的灰度值(即彩色變黑白)屎慢。
??在把 data 數(shù)組回寫到 imageData 對象后,調(diào)用putImageData() 方法把圖像數(shù)據(jù)繪制到畫布上忽洛。最終得到了圖像的黑白版腻惠。
??當(dāng)然,通過操作原始像素值不僅能實現(xiàn)灰階過濾欲虚,還能實現(xiàn)其他功能集灌。

??只有在畫布“干凈”的情況下(即圖像并非來自其他域),才可以取得圖像數(shù)據(jù)复哆。如果畫布“不干凈”欣喧,那么訪問圖像數(shù)據(jù)時會導(dǎo)致 JavaScript 錯誤。

2.11梯找、合成

??還有兩個會應(yīng)用到 2D 上下文中所有繪制操作的屬性:globalAlpha 和 globalCompositionOperation唆阿。
??其中,globalAlpha 是一個介于 0 和 1 之間的值(包括 0 和 1)锈锤,用于指定所有繪制的透明度驯鳖。默認(rèn)值為 0闲询。如果所有后續(xù)操作都要基于相同的透明度,就可以先把 globalAlpha 設(shè)置為適當(dāng)值浅辙,然后繪制扭弧,最后再把它設(shè)置回默認(rèn)值 0。示例:

// 繪制紅色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);

// 修改全局透明度
context.globalAlpha = 0.5;

// 繪制藍(lán)色矩形
context.fillStyle = "rgba(0, 0, 255, 1)";
context.fillRect(30, 30, 50, 50);

// 重置全局透明度
context.globalAlpha = 0; 

??在上述例子中记舆,我們把藍(lán)色矩形繪制到了紅色矩形上面寄狼。因為在繪制藍(lán)色矩形前,globalAlpha 已經(jīng)被設(shè)置為 0.5氨淌,所以藍(lán)色矩形會呈現(xiàn)半透明效果泊愧,透過它可以看到下面的紅色矩形。

??第二個屬性 globalCompositionOperation 表示后繪制的圖形怎樣與先繪制的圖形結(jié)合盛正。這個屬性的值是字符串删咱,可能的值如下。

  • source-over(默認(rèn)值):后繪制的圖形位于先繪制的圖形上方豪筝。
  • source-in:后繪制的圖形與先繪制的圖形重疊的部分可見痰滋,兩者其他部分完全透明。
  • source-out:后繪制的圖形與先繪制的圖形不重疊的部分可見续崖,先繪制的圖形完全透明敲街。
  • source-atop:后繪制的圖形與先繪制的圖形重疊的部分可見,先繪制圖形不受影響严望。
  • destination-over:后繪制的圖形位于先繪制的圖形下方多艇,只有之前透明像素下的部分才可見。
  • destination-in:后繪制的圖形位于先繪制的圖形下方像吻,兩者不重疊的部分完全透明峻黍。
  • destination-out:后繪制的圖形擦除與先繪制的圖形重疊的部分。
  • destination-atop:后繪制的圖形位于先繪制的圖形下方拨匆,在兩者不重疊的地方姆涩,先繪制的圖形會變透明。
  • lighter:后繪制的圖形與先繪制的圖形重疊部分的值相加惭每,使該部分變亮骨饿。
  • copy:后繪制的圖形完全替代與之重疊的先繪制圖形。
  • xor:后繪制的圖形與先繪制的圖形重疊的部分執(zhí)行“異或”操作台腥。

??這個合成操作實際上用語言或者黑白圖像是很難說清楚的宏赘。要了解每個操作的具體效果,請參見 https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html览爵。推薦使用 IE9+或 Firefox 4+訪問前面的網(wǎng)頁置鼻,因為這兩款瀏覽器對 Canvas 的實現(xiàn)最完善。下面來看一個例子蜓竹。

// 繪制紅色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);

// 設(shè)置合成操作
context.globalCompositeOperation = "destination-over";

// 繪制藍(lán)色矩形
context.fillStyle = "rgba(0,0,255,1)";
context.fillRect(30, 30, 50, 50);

??如果不修改 globalCompositionOperation箕母,那么藍(lán)色矩形應(yīng)該位于紅色矩形之上储藐。但把 globalCompositionOperation 設(shè)置為"destination-over"之后,紅色矩形跑到了藍(lán)色矩形上面嘶是。
??在使用 globalCompositionOperation 的情況下钙勃,一定要多測試一些瀏覽器。因為不同瀏覽器對這個屬性的實現(xiàn)仍然存在較大的差別聂喇。Safari 和 Chrome 在這方面還有問題辖源,至于有什么問題,大家可以比較在打開上述頁面的情況下希太,IE9+和 Firefox 4+與它們有什么差異克饶。

小結(jié)

??HTML5 的<canvas>元素提供了一組 JavaScript API,讓我們可以動態(tài)地創(chuàng)建圖形和圖像誊辉。圖形是在一個特定的上下文中創(chuàng)建的矾湃,而上下文對象目前有兩種。第一種是 2D 上下文堕澄,可以執(zhí)行原始的繪圖操作邀跃,比如:

  • 設(shè)置填充、描邊顏色和模式
  • 繪制矩形
  • 繪制路徑
  • 繪制文本
  • 創(chuàng)建漸變和模式

??第二種是 3D 上下文蛙紫,即 WebGL 上下文拍屑。WebGL 是從 OpenGL ES 2.0 移植到瀏覽器中的,而 OpenGLES 2.0 是游戲開發(fā)人員在創(chuàng)建計算機(jī)圖形圖像時經(jīng)常使用的一種語言坑傅。WebGL 支持比 2D 上下文更豐富和更強(qiáng)大的圖形圖像處理能力僵驰,比如:

  • 用 GLSL(OpenGL Shading Language,OpenGL 著色語言)編寫的頂點和片段著色器
  • 支持類型化數(shù)組裁蚁,即能夠?qū)?shù)組中的數(shù)據(jù)限定為某種特定的數(shù)值類型
  • 創(chuàng)建和操作紋理

??目前矢渊,主流瀏覽器的較新版本大都已經(jīng)支持<canvas>標(biāo)簽。同樣地枉证,這些版本的瀏覽器基本上也都支持 2D 上下文。但對于 WebGL 而言移必,目前還只有 Firefox 4+和 Chrome 支持它室谚。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市崔泵,隨后出現(xiàn)的幾起案子秒赤,更是在濱河造成了極大的恐慌,老刑警劉巖憎瘸,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件入篮,死亡現(xiàn)場離奇詭異,居然都是意外死亡幌甘,警方通過查閱死者的電腦和手機(jī)潮售,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門痊项,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人酥诽,你說我怎么就攤上這事鞍泉。” “怎么了肮帐?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵咖驮,是天一觀的道長。 經(jīng)常有香客問我训枢,道長托修,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任恒界,我火速辦了婚禮诀黍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘仗处。我一直安慰自己眯勾,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布婆誓。 她就那樣靜靜地躺著吃环,像睡著了一般。 火紅的嫁衣襯著肌膚如雪洋幻。 梳的紋絲不亂的頭發(fā)上郁轻,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天,我揣著相機(jī)與錄音文留,去河邊找鬼好唯。 笑死,一個胖子當(dāng)著我的面吹牛燥翅,可吹牛的內(nèi)容都是我干的骑篙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼森书,長吁一口氣:“原來是場噩夢啊……” “哼靶端!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起凛膏,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤杨名,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后猖毫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體台谍,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年吁断,在試婚紗的時候發(fā)現(xiàn)自己被綠了趁蕊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坞生。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖介衔,靈堂內(nèi)的尸體忽然破棺而出恨胚,到底是詐尸還是另有隱情,我是刑警寧澤炎咖,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布赃泡,位于F島的核電站,受9級特大地震影響乘盼,放射性物質(zhì)發(fā)生泄漏升熊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一绸栅、第九天 我趴在偏房一處隱蔽的房頂上張望级野。 院中可真熱鬧,春花似錦粹胯、人聲如沸蓖柔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽况鸣。三九已至,卻和暖如春竹观,著一層夾襖步出監(jiān)牢的瞬間镐捧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工臭增, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留懂酱,地道東北人。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓誊抛,卻偏偏與公主長得像列牺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子芍锚,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 本章內(nèi)容 理解 元素 繪制簡單的 2D 圖形 使用 WebGL 繪制 3D 圖形 這個元素負(fù)責(zé)在頁面中設(shè)定一個區(qū)域...
    悶油瓶小張閱讀 849評論 0 0
  • --繪圖與濾鏡全面解析 概述 在iOS中可以很容易的開發(fā)出絢麗的界面效果昔园,一方面得益于成功系統(tǒng)的設(shè)計,另一方面得益...
    韓七夏閱讀 2,731評論 2 10
  • Core Graphics Framework是一套基于C的API框架并炮,使用了Quartz作為繪圖引擎。它提供了低...
    ShanJiJi閱讀 1,537評論 0 20
  • 遇見你,我就好像遇見了真正的自己 我說澜搅,你欠我很多擁抱 你說伍俘,我是你不能擁抱的短暫理想 那你就努力伸長手夠啊 我知...
    洛邀閱讀 485評論 0 0