著各大平臺小程序的快速放量,開發者遇到越來越多的平臺適配問題。各平臺小程序的性能優化方法也各不相同,我們該如何應對?DCloud CTO 崔紅保在 GMTC 深圳 2019(全球大前端技術大會)分享了《小程序的未來方向》,介紹了小程序技術架構、性能卡點以及各平臺優化方案,對于小程序未來的技術更迭,提出了小程序在未來可能的發展方向。本文根據演講內容整理而成。
簡單介紹一下我自己,中年碼農,跨平臺開發領域的老兵。在那個翻蓋摩托羅拉手機代表著先進和時髦的年代,我就開始參與 “window mobile/j2me/symbain” 等系統的跨平臺研發管理工作,可能很多同學都沒見過那些手機。到后來的移動互聯網時代及當下的小程序時代,我也一直在深度參與其中,持續輸出“Hybrid App”引擎、前端 UI 庫(mui)及小程序跨端開發框架(uni-app)。目前在 DCloud 任職 CTO,同時兼 “uni-app” 產品負責人。
羅馬不是一天建成的,小程序也不是一天發明的。小程序這種介于 H5 和 Native App 之間的特殊應用形態,從探索到成熟,經歷了哪些過程?我們首先帶大家回顧梳理一下。然后,從現有技術架構出發,分析小程序當下幾個主要性能坑點。各家小程序引擎為解決這些坑點,做了哪些完善工作。比如,大家知道小程序是以 Web 渲染為主、原生渲染為輔,那引入原生渲染后,引發了哪些新的問題?為解決這些問題,微信提出了同層渲染的方案,同層渲染在技術層面上又是如何實現的?最后從當前已知問題出發,對于小程序未來的技術更迭,拋出一些我們認為的可能方向,供大家參考。
HTML5 于 2007 年在 W3C 立項,與 iPhone 發布同年。
喬布斯曾期待 HTML5 能幫助 iPhone 打造起應用生態系統。但 HTML5 的發展速度并不如預期,雖然它成功地打破了 IE+Flash 壟斷的局面,卻沒有達到承載優秀的移動互聯網體驗的地步。
蘋果公司在 iPhone 站穩腳跟后,緊接著發布了自己的 App Store,開啟了移動互聯網的原生應用時代。
大家知道現在手機端主要是 iOS、Android 兩大系統,實際上在早期有 3 大系統競爭,還有一個就是諾基亞的 MeeGo 系統,MeeGo 采用 C + HTML5 的雙模應用生態策略。然而,C 的開發難度太大,HTML5 體驗又不行,所以后來 MeeGo 就掉隊了;與之對應,Android 依靠 Java 技術生態,在競爭中脫穎而出。
于是在移動互聯網初期,應用生態被定了基調 —— 原生開發。
國內有一批做瀏覽器的廠商,嘗試去改進 HTML5。比如,百度在 2013 年的百度世界大會上發布了輕應用,通過給 WebView 擴展原生能力,補充 JS API,讓 HTML5 應用可以實現更多功能。
這類業務發展的頂峰,是微信在 2015 年初發布的微信 JS SDK,作為國內事實上最大的手機瀏覽器,微信為它的瀏覽器內核擴充了大量 JS API,讓開發者可以用 JS 調用微信支付、掃碼等眾多 HTML5 做不到的功能。
不過這類業務沒有取得成功,HTML5 的問題不止是功能不足,性能體驗是更嚴重的問題。而體驗問題,不是簡單地擴展 JS 能力能搞定的。
與瀏覽器不同,Hybrid 應用是另一個細分領域,開發者使用 JS 編寫應用,為了讓 JS 應用更接近原生應用的功能體驗,這個行業的從業者做出了很多嘗試。我們 DCloud 公司是業內主流 Hybrid App 引擎提供方之一,我們提出了改進 HTML5 的“性能功能”障礙的解決方案 —— 通過工具、引擎優化、開發模式調整,讓開發者可以通過 JS 寫出更接近原生 App 體驗的應用。
多 WebView 模式,原生接管轉場動畫、下拉刷新、Tab 分頁,預載 WebView……各種優化技術不停迭代,終于讓 Hybrid 應用取得了性能體驗的突破。
Hybrid 應用和輕應用、微信 JS SDK 等基于瀏覽器增加方案相比,還有一個巨大的差別:一個是 Client/Server,一個是 Browser/Server。簡單來說,Hybrid 應用是 JS 編寫的需要安裝的 App,而輕應用是在線網頁。
C/S 的應用在每次頁面加載時,僅需要聯網獲取 JSON 數據;而 B/S 應用除了 JSON 數據外,還需要每次從服務器加載頁面 DOM、樣式、邏輯代碼,所以 B/S 應用的頁面加載很慢,體驗很差。
可是這樣的 C/S 應用雖然體驗好,卻失去了 HTML5 的動態性,仍然需要安裝、更新,無法即點即用、直達二級頁面。
那么 C/S 應用的動態性是否可以解決呢?對此, DCloud 率先提出了“流應用”概念,把之前 Hybrid 應用里的運行于客戶端的 JS 代碼,先打包發布到服務器,制定流式加載協議,手機端引擎動態下載這些 JS 代碼到本地,并且為了第一次加載速度更快,實現了應用的邊下載邊運行。
就像流媒體的邊下邊播一樣,應用也可以實現邊用邊下。
在這套方案的保障下,終于解決了之前的各種難題:讓 JS 應用功能體驗達到原生,并且可即點即用、直達二級頁面。
接著就是微信小程序,最初的名字實際上是微信應用號,之后改名為小程序,2016 年 9 月份內測,2017 年 1 月正式發行,再之后阿里巴巴、手機廠商聯盟、百度、,陸續推出了自己的小程序平臺,小程序時代滾滾而來。
2018 年 9 月,微信推出云開發,這個功能我們認為是小程序發展歷史上的一個重要節點,它可以讓前端工程師從前到后將所有業務閉環實現,減少前后端的溝通成本、人力成本、運維成本,屬于開發模式的重大升級。與之前的前端同學既可通過 JS/CSS 編寫前端 UI,又可通過 “Node.js” 寫后端業務,這種所謂全棧開發模式相比,云開發有更好的優勢,因為前端同學對于 DB 優化、彈性擴容、攻擊防護、災備處理等方面還是有經驗欠缺的,但云開發將這些都封裝好了,真正做到僅專注業務實現,其它都委托云廠商服務。
這是一個比較通用的小程序架構,目前幾家小程序架構設計大致都是這樣的(快應用的區別是視圖層只有原生渲染)。
大家知道小程序是一個邏輯、視圖層分離的架構。
邏輯層就是上圖左上角這塊,小程序中開發的所有頁面 JS 代碼,最后都會打包合并到邏輯層,邏輯層除了執行開發者的業務 JS 代碼外,還需處理小程序框架的內置邏輯,比如 App 生命周期管理。
視圖層就是上圖右上角這塊,用戶可見的 UI 效果、可觸發的交互事件在視圖層完成,視圖層包含 Web 組件、原生組件兩種,也就是小程序是原生 +Web 混合渲染的模式,這塊后面會詳細講。
邏輯層最后運行在 JS CORE 或 V8 環境中;JS CORE 既不是 DOM 環境,也不是 Node 環境,你是無法使用 JS 中的 DOM 或 BOM 對象的,你能調用的僅僅是 ECMAScript 標準規范中所給出的方法。
那如果你要發送網絡請求怎么辦?window.XMLHttpRequest 是無法使用的(當然即使可以調用,在 iOS 的 WKWebView 中也存在更嚴格的跨域限制,會有問題)。這時候,網絡請求就需要通過原生的網絡模塊來發送,JS CORE 和原生之間呢,就需要這個 JS Bridge 來通訊。
小程序這種架構,最大的好處是新頁面加載可以并行,讓頁面加載更快,且不卡轉場動畫;但同時也引發了部分性能坑點,今天主要介紹 3 點:
我們從“swipeaction”這個例子講起,需求是用戶在列表項上向左滑動,右側隱藏的菜單跟隨用戶手勢平滑移動。
若想在小程序架構上實現流暢的跟手滑動,是很困難的,為什么?
回顧一下小程序架構,小程序的運行環境分為邏輯層和視圖層,分別由 2 個線程管理,小程序在視圖層與邏輯層兩個線程間提供了數據傳輸和事件系統。這樣的分離設計,帶來了顯而易見的好處:
環境隔離,既保證了安全性,同時也是一種性能提升的手段,邏輯和視圖分離,即使業務邏輯計算非常繁忙,也不會阻塞渲染和用戶在視圖層上的交互。
但同時也帶來了明顯的壞處:
視圖層(WebView)中不能運行 JS,而邏輯層 JS 又無法直接修改頁面 DOM,數據更新及事件系統只能靠線程間通訊,但跨線程通信的成本極高,特別是需要頻繁通信的場景。
基于這樣的架構設計,我們回到“swipeaction”,分析一次 touchmove 的操作,小程序內部的響應過程:
(1)用戶拖動列表項,視圖層觸發 touchmove 事件,經 Native 層中轉通知邏輯層(邏輯層、視圖層不是直接通訊的,需 Native 中轉),即下圖中的?、?兩步;
(2)邏輯層計算需移動的位置,然后再通過 setData 傳遞位置數據到視圖層,中間同樣會由微信客戶端(Native)做中轉,即下圖中的?、?兩步。
實際上,用戶滑動過程中,touchmove 的回調觸發是非常頻繁的,每次回調都需要 4 個步驟的通訊過程,高頻率回調導致通訊成本大幅增加,極有可能導致頁面卡頓或抖動。為什么會卡頓,因為通訊太過頻繁,視圖層無法在 16ms 內完成 UI 更新。
為解決這種通訊阻塞的問題,各家小程序都在逐步提供對應的解決方案,比如微信的 WXS、支付寶的 SJS、百度的 Filter,但每家小程序支持情況不同,詳細見下表。
另外,微信的“關鍵幀動畫”、百度的“animation-view” Lottie 動畫,也是為減少頻繁通訊的一種變更方式。
其實,通訊阻塞是業界普遍存在的一個問題,不止小程序,“React Native”“Weex”等同樣存在通訊阻塞的問題。只不過“React Native”“Weex”的視圖層是原生渲染,而小程序是 Web 渲染。我們下面以“Weex”為例來說明。
大家知道,“Weex”底層使用的 JS-Native Bridge,這個 Bridge 使得 JS 和 Native 之間的通信會有固定的性能損耗。
繼續以上述“swipeaction”為例,要實現列表項菜單的跟手滑動,大致需經如下流程:
(1)在 UI 視圖上綁定 touch 事件(或 pan 事件);
(2)當手勢觸發時, Native UI 層將手勢事件通過 Bridge 傳遞給 JS 邏輯層 , 這產生了一次 Native UI 到 JS 邏輯的通信,即下圖中的?、?兩步 ;
(3)JS 邏輯在接收到事件后,根據手指移動的偏移量驅動界面變化,這又會產生一次 JS 到 Native UI 的通信,即下圖中的?、?兩步。
同樣,手勢回調事件觸發的頻率是非常高的,頻繁的的通信帶來的時間成本很可能導致界面無法在 16ms 中完成繪制,卡頓也就產生了。
“Weex”為解決通訊阻塞,提供了“BindingX”解決方案,這是一種稱之為“Expression Binding”的機制,簡要介紹一下:
(1)接收手勢事件的視圖,在移動過程中的偏移量以“x,y”兩個變量表示;
(2)期望改變(跟隨移動)的視圖,變化的屬性為“translateX”和“translateY”,對應變化的偏移量以“f(x),f(y)”表達式表示;
(3)將”交互行為 " 以表達式的方式描述,并提前預置到 Native UI 層;
(4)交互觸發時,Native UI 根據其內置的表達式解析引擎,去執行表達式,并根據表達式執行的結果驅動視圖變換,這個過程無需和 JS 邏輯通訊。
偽代碼 - 摘錄自 Weex 官網
復制代碼
{ anchor: foo_view.ref // ----> 這是 " 產生手勢的視圖 " 的引用 props: [ { element: foo_view.ref, // ----> 這是 " 期望改變的視圖 " 的引用 expression: f(x)=x, // ----> 這是具體的表達式 property: translateX // ----> 這是期望改變的屬性 }, { element: foo_view.ref, expression: f(y)=y, // ----> y 屬性 property: translateY } ]}
“React Native”同樣存在類似問題,為避免頻繁的通信,“React Native”生態也有對應方案,比如“Animated”組件及 Lottie 動畫支持。以 “Animated”組件為例,為實現流暢的動畫效果,該組件采用了聲明式的 API,在 JS 端僅定義了輸入與輸出以及具體的 transform 行為,而真正的動畫是通過 Native Driver 在 Native 層執行,這樣就避免了頻繁的通信。然而,聲明式的方式能夠定義的行為有限,無法勝任交互場景。
“uni-app”在 App 端同樣面臨通訊阻塞的問題,我們目前的方案是采用類似微信 WXS 的機制(內部叫“renderjs”),但放開了 WXS 中無法獲取頁面 DOM 元素的限制,比如下圖中多個小球同時移動的 canvas 動畫,“uni-app”在 App 端的實現方案是:
(1)renderjs 中獲取 canvas 對象;
(2)基于 web 的 canvas 繪制動畫,而非原生 canvas 繪制。
Tips:大家需要注意,并不是所有場景都是原生性能更好,小程序架構下,如上多球同時移動的動畫,原生 canvas 并不如在 WXS(renderjs)中直接調用 Web canvas
下表總結了跨端框架在通訊阻塞方面的解決方案:
小程序架構存在通訊阻塞問題,廠商為解決這個問題,創造了“WXS”腳本語言及關鍵幀動畫等方式,但這些都是廠商維度的優化方案。我們作為小程序開發者,在性能優化方面,又能做哪些工作呢?
小程序開發性能優化,核心就是“setData”的調用,你能做只有兩件事情:
(1)減少 setData 調用次數
假設我們有更改多個變量值的需求,示例如下:
change:function(){
? ? this.setData({a:1});
? ? ... // 其它業務邏輯
? ? this.setData({b:2});
? ? ... // 其它業務邏輯
? ? this.setData({c:3});
? ? ... // 其它業務邏輯
? ? this.setData({d:4});
}
如上,4 次調用“setData”,會引發 4 次邏輯層、視圖層數據通訊。這種場景,開發者需意識到“setData”有極高的調用代價,自己需手動調整代碼,合并數據,減少數據通訊次數。
部分小程序三方框架已內置數據合并的能力,比如“uni-app”在 Vue runtime 上進行了深度定制,開發者無需關注“setData”的調用代價,可放心編寫如下代碼:
change:function(){
? ? this.a=1;
? ? ... // 其它業務邏輯
? ? this.b=2;
? ? ... // 其它業務邏輯
? ? this.c=3;
? ? ... // 其它業務邏輯
? ? this.d=4;
}
如上 4 次賦值,uni-app 運行時會自動合并成“{“a”:1,“b”:2,“c”:3,“d”:4}”一條記錄,調用一次“setData”完成所有數據傳遞,大幅降低 setData 的調用頻次,結果如下圖:
減少“setData”調用次數,還有個注意點:后臺頁面(用戶不可見的頁面)應避免調用“setData”。
(2)數據差量更新
假設我們有一個 “列表頁 + 上拉加載” 的場景,初始化列表項為 “item1 ~ item4”,用戶上拉后要向列表追加 4 條新記錄 “item5 ~ item8”,小程序代碼如下:
page({
? ? data:{
? ? ? ? list:['item1','item2','item3','item4']
? ? },
? ? change:function(){
? ? ? ? let newData=['item5','item6','item7','item8'];
? ? ? ? this.data.list.push(...newData); // 列表項新增記錄
? ? ? ? this.setData({
? ? ? ? ? ? list:this.data.list
? ? ? ? })
? ? }
})
如上代碼,change 方法執行時,會將 list 中的 “item1 ~ item8”8 個列表項通過“setData”全部傳輸過去,而實際上變化的數據只有“item5 ~ item8”。
開發者在這種場景下,應通過差量計算,僅通過“setData”傳遞變化的數據,如下是一個示例代碼:
page({
? ? data:{
? ? ? ? list:['item1','item2','item3','item4']
? ? },
? ? change:function(){
? ? ? ? // 通過長度獲取下一次渲染的索引
? ? ? ? let index=this.data.list.length;
? ? ? ? let newData=['item5','item6','item7','item8'];
? ? ? ? let newDataObj={};// 變化的數據
? ? ? ? newData.forEach((item)=> {
? ? ? ? ? ? newDataObj['list[' + (index++) + ']']=item;// 通過 list 下標精確控制變更內容
? ? ? ? });
? ? ? ? this.setData(newDataObj) // 設置差量數據
? ? }
})
每次都手動計算差量變更數據是繁瑣的,新手不理解小程序原理的話,也容易忽略這些性能點,給 App 埋下性能坑點。
此處,建議開發者選擇成熟的第三方小程序框架,這些框架已經自動封裝差量數據計算,對開發者更友好。比如,“uni-app”借鑒了 “westore JSON Diff”庫,在調用 setData 之前,會先比對歷史數據,精確高效計算出有變化的差量數據,然后再調用 setData,僅傳輸變化的數據,這樣可實現傳遞數據量的最小化,提升通訊性能。如下,是一個示例代碼:
export default{
? ? data(){
? ? ? ? return {
? ? ? ? ? ? list:['item1','item2','item3','item4']
? ? ? ? }
? ? },
? ? methods:{
? ? ? ? change:function(){
? ? ? ? ? ? let newData=['item5','item6','item7','item8'];
? ? ? ? ? ? this.list.push(...newData) // 直接賦值,框架會自動計算差量數據
? ? ? ? }
? ? }
}
Tips:如上 change 方法執行時,僅會將 list 中的 “item5 ~ item8”4 個新增列表項傳輸過去,實現了 setData 傳輸量的極簡化。
(3)組件差量更新
下圖是一個微博列表截圖:
假設當前有 200 條微博,用戶對某條微博點贊,需實時變更其點贊數據(狀態);在傳統模式下,一條微博的點贊狀態變更,會將整個頁面 (Page) 的數據全部通過 setData 傳遞過去,這個消耗是非常高的;而即使通過之前介紹,通過差量計算的方式獲取變更數據,這個 Diff 遍歷范圍也很大,計算效率極低。
如何實現更高性能的微博點贊?這其實就是組件更新的典型場景。
合適的方式應該是,將每條微博封裝成一個組件,用戶點贊后,僅在當前組件范圍內計算差量數據(可理解為 Diff 范圍縮小為原來的 1/200),這樣效率才是最高的。
提醒大家注意,并不是所有小程序三方框架都已實現自定義組件,只有在基于自定義組件模式封裝的框架中,性能才會大幅提升;如果三方框架是基于老的“template”模板封裝的組件開發,則性能并不會有明顯改善,其 Diff 對比范圍依然是 Page 頁面級的。
大家知道,小程序當中有一類特殊的內置組件——原生組件,這類組件有別于 WebView 渲染的內置組件,他們是由原生客戶端渲染的。
小程序中的原生組件,從使用方式上來說,主要分為三類:
除了上面提到的這些之外,其它基本都是 Web 渲染。所以說,小程序是混合渲染模式,Web 渲染為主,原生渲染為輔。
(1)為什么要引入混合渲染
接下來的問題,為什么要引入原生渲染?以及為什么僅針對這幾個組件提供了原生增強?其他組件為什么沒有做原生實現?
這就需要我們針對每個組件單獨進行分析思考,這里舉了幾個例子:
提到“input”控件的原生化,可以稍微發散一下。
小程序中原生 input 控件的通用做法是,未獲取焦點時以 Web 控件顯示,但在獲取焦點時,繪制一個原生 input,蓋在 Web input 上方,此時,用戶看見的鍵盤即為原生 input 所對應的鍵盤,原生彈出鍵盤是可自定義按鈕(如上圖中下一步、send 按鈕)。這種做法存在一個缺陷: Web 和原生,畢竟不同渲染引擎,在鍵盤彈出和關閉時,對應 input 的“placeholder”會閃爍。
在 Android 平臺,還有一種做法是基于 WebKit 改造,定制彈出鍵盤樣式;這種方案,在鍵盤彈出和關閉時,input 控件都是 Web 實現的,故不存在“placeholder”閃爍的問題。
(2)混合渲染引發的問題
原生組件雖然帶來了更豐富的特性及更好的性能,但同時也引入了一些新的問題,比如:
當然,并不是所有小程序都存在這種問題,部分小程序通過修改自帶的 WebView 內核,實現了 WebView 也可以使用 rom 主題字體,比如微信、QQ、支付寶;其他小程序(百度、頭條),WebView 仍然無法渲染為 rom 主題字體。
(3) 混合渲染改進方案
既然混合渲染有這些問題,對應就會有解決方案,目前已有的方案如下。
既然其它組件無法覆蓋到原生組件上,那就創造出一種新的組件,讓這個新組件可以覆蓋到 video 或 map 上?!癱over-view/cover-image”就是基于這種需求創造出來的新組件;其實它們也是原生組件,只不過層級略高,可以覆蓋在 map、video、canvas、camera 等原生組件上。
目前除了字節跳動外,其它幾家小程序均已支持“cover-view/cover-image”。
cover-view/cover-image 在一定程度上緩解了分層覆蓋的問題,但也有部分限制,比如嚴格的嵌套順序。
既然分層有問題,那就消除分層,從 2 層變成 1 層,所有組件都在一個層中,“z-index”豈不就可生效了?
這個小目標說起來簡單,具體實現還是很復雜的。
拋開小程序當前架構實現,解決混合渲染最直接的方案,應該更換渲染引擎,全部基于原生渲染,video/map 和 image/view 均為原生控件,層級相同,層級遮蓋問題自然消失。這正是“uni-app”在 App 端的推薦方案。
當前 Web 渲染為主、原生渲染為輔的主流小程序現狀,如何實現同層渲染?
基于我們的分析研究,這里簡單講解一下同層渲染實現的方案,和微信真實實現可能會有出入(目前僅微信一家實現了同層渲染)。
(1) iOS 平臺
小程序在 iOS 端使用 WKWebView 進行渲染,WKWebView 在內部采用的是分層渲染,一般會將多個 DOM 節點,合并到一個層上進行渲染。因此,DOM 節點和層之間不存在一一對應關系。但是,一旦將一個 DOM 節點的 CSS 屬性設置為 “overflow: scroll” 后,WKWebView 便會為其生成一個 WKChildScrollView,且 WebKit 內核已經處理了 WKChildScrollView 與其他 DOM 節點之間的層級關系,這時 DOM 節點就和層之間有一一對應關系了。
小程序 iOS 端的同層渲染可基于 WKChildScrollView 實現,主要流程如下:
(2)Android 平臺
小程序在 Android 端采用 Chromium 作為 WebView 渲染層,和 iOS 的 WKWebView 不同,是統一渲染的,不會分層渲染。但 Chromium 支持 WebPlugin 機制,WebPlugin 是瀏覽器內核的一個插件機制,可用來解析“< embed >”。Android 端的同層渲染可基于 “< embed >”加 Chromium 內核擴展來實現,大致流程如下:
這個流程相當于給 WebView 添加了一個外置插件,且“< embed >”節點是真正的 DOM 節點,可將更多的樣式作用于該節點上。
如果要探討小程序接下來的技術升級方向,我們認為應該在用戶體驗、開發效率兩個方向上努力。
先說用戶體驗的問題,主要也是兩個方面:
如果你也想快速搭建的自己的小程序引擎,并更優的解決如上體驗問題,該怎么辦?
uni-app 發行到 App 端,實際上就是一個完整的小程序引擎,DCloud 會在近期將這個引擎完整開源,歡迎大家基于 uni-app 小程序 SDK 快速打造自己的小程序平臺。
uni-app 小程序 SDK 具備如下幾個特征:
開發效率應該從跨端、跨云兩個維度進行分析。
目前的小程序都帶有明顯的廠家屬性,每個廠家各不相同。比如,阿里內部有多套小程序(支付寶、淘寶、釘釘等),幸好阿里內部目前已基本統一。但騰訊體系下,微信和 QQ 小程序依然是兩隊人馬,兩套規范。
小程序之前是手機端的,2019 年 360 出了 PC 端小程序。
接下來,會不會還有其它廠家推出自己的小程序?會不會有新的端的出現?比如,面向電視的小程序、面向車載的小程序?
一切皆有可能。
逐水草而居是人類的本能,追求流量依然是互聯網的制勝法寶。當前的小程序宿主,都是億級流量入口,且各家流量政策不同。比如,微信的流量雖然很大,但有各種限制;百度和頭條是支持廣告投放的,通過廣告投放,可以快速獲得大量較為精準的用戶;百度小程序還有個 Web 化的功能,可以將 Web 的搜索流量,轉化成小程序的流量。
面對眾多小程序平臺及各自巨大的入口流量,開發者如何應對?
等待 W3C 的小程序標準統一,短期不太現實。當下,若想將業務快速觸達多家小程序,借助跨端框架應該是唯一可行的方案。
開發商借助“uni-app”或其它跨端框架,雖然已可以開發所有前端應用。但仍然需要雇傭 PHP 或 Java 等后臺開發人員,既有后端人員成本,又有前 / 后端溝通成本。
騰訊、阿里、百度小程序雖陸續上線了云開發,但它們均只支持自己的小程序,無法跨端,分散的服務器對開發商更不可取。
故我們認為跨廠商的 Serverless 是接下來的一個重點需求,開發者在一個云端存儲所有業務數據及后端邏輯,然后將前端小程序發行到各家小程序平臺,也就是“一云多端”模式。
基于小程序的現狀,我們也許可以總結一下小程序技術上的可能方向:
作者介紹:
崔紅保,DCloud CTO,Uni-App 團隊負責人,開發了 2 個 Github Star 上萬的流行項目。有 10 年以上研發管理經驗,在跨平臺引擎、前端 UI、小程序性能優化等方面有豐富的實踐經驗。
性漸變和徑向漸變《HTML5系列教程19》
線性漸變和徑向漸變《HTML5系列教程19》
漸變在我們的日常生活中是一種隨處可見的非常普遍的一種視角形象。Canvas為實現漸變效果提供了很好的解決方案。在HTML5中主要有兩種漸變方式,一種是沿著直線的漸變方式,另一種是從一個點或圓的半徑向四周漸變的方式,我們把這兩種方式叫做線性漸變和徑向漸變。今天我們主要介紹這兩種漸變的繪制方法。
1.線性漸變的繪制方法
Canvas提供了用于創建線性漸變的函數createLinearGRadient(x0,y0,x1,y1),坐標點是(x0,y0)指定了線性漸變的起點,坐標點(x1,y1)指定了線性漸變的終點,如果這兩個坐標點在一條水平線上,那么就會創建水平線性漸變;如果這兩個坐標點在一條垂直線上,那么將創建垂直線性漸變;如果這兩個坐標點連線是一條傾斜的直線,那么將創建傾斜線性漸變。那么假設現在我們要創建一個寬度為300的水平線性漸變,示例代碼如圖所示:
HTML5中寬度為300的水平線性漸變的繪制代碼
有了一個漸變對象之后,我們就需要定義漸變的顏色了。在Canvas中使用addColorStop(stop,color)函數來定義漸變的顏色,參數stop表示開始漸變位置占漸變區域的百分比,為0~1之間的任意值,參數color為顏色樣式。在實際應用中,至少要添加兩種以上顏色才能達到漸變效果。例如要創建從紅色到藍色的漸變,可以使用圖中這個示例代碼:
HTML5中漸變顏色的定義
接下來我們需要設置Canvas內容的fillStyle為當前的漸變對象,并且繪制圖形,比如一個矩形或一條直線。所以,為了看到漸變效果,我們還需要以下代碼:
HTML5中繪制漸變圖形
到這里,一個線性漸變的圖形就繪制完成了,完整的示例代碼如圖:
HTML5中繪制紅藍漸變的代碼示例
在谷歌瀏覽器上預覽效果如圖:
HTML5中繪制紅藍漸變的效果示例
2.徑向漸變的繪制
徑向漸變與線性漸變的實現方式基本類似,在Canvas中使用createRadialGradient(x0,y0,r0,x1,y1,r1)函數創建一個沿兩個圓之間的錐面繪制漸變。前三個參數代表圓心為(x0,y0),半徑為r0的開始圓,后三個參數代表圓心為(x1,y1),半徑為r1的結束圓。創建該對象后,仍需要使用addColorStop函數定義漸變顏色,并設置徑向漸變對象為fillStyle的當前漸變對象,最后繪制一個漸變圖形,完成徑向漸變的繪制。繪制徑向漸變的示例代碼如圖所示:
HTML5中徑向漸變的繪制代碼示例
在谷歌瀏覽器上預覽效果如圖:
HTML5中徑向漸變的繪制效果示例
還有一點要注意的是在繪制徑向漸變時,可能會因為Canvas的寬度或高度設置不合適,導致徑向漸變顯示不完整,這時候就要考慮跳轉Canvas的尺寸了,以便能完整顯示徑向漸變的效果。這一次線性漸變和徑向漸變就是這些了。謝謝大家的觀看。祝大家:身體健康、生活愉快!
html5Canvas的知識點,是開發的必備技能,在實際工作中也常常會涉及到。
最近熬夜總結html5Canvas相關的知識點,大家一起看一下吧:
Canvas使用的場景有:1,動畫;2,H5游戲;3,圖表。
效果動畫,加載Loading:
H5游戲效果:
對于Canvas需要掌握:
Canvas坐標體系
畫直線,矩形和原型
beginPath和closePath
描邊和填充樣式
Canvas中的圖形變換,漸變,文字和圖片
Canvas中的圖形變換
圖形變換都是針對坐標系來說的:
save和restore
用來保存和恢復上下文的環境ctx,一般成對出現
Canvas中的漸變
(xStart,yStart)是線段的起點,(xEnd,yEnd)是線段終點。起點到終點之間的顏色呈漸變。
ctx.createRadialGradient(xStart,yStart, radiusStart,xEnd,yEnd,radiusEnd);
(xStart,yStart)是第一個圓的原心,radiusStart是第一個圓的半徑,(xEnd,yEnd)是第二個圓的原心,radiusEnd是第二個圓的半徑
第一圓到第二個圓之間的顏色呈現漸變。
Canvas中的文字
描邊文字:ctx.strokeText(text,x,y)
填充文字:ctx.fillText(text,x,y);
設置字體樣式:ctx.font
設置垂直對齊方式:ctx.textBaseline
Canvas圖片
繪制圖片3種方法
在image加載完成之后繪制:
示例:
var img=new Image();img.src='logo.png';img.onload=function() { ctx.drawImage(img,0,0,40,40,0,0,80,80);}
Canvas繪制
Canvas圖形畫刷
ctx.createPattern可以創建一個畫刷模式,進而可以設置到fillStyle里,進行畫刷的填充。
type取值:
Canvas像素操作
var imageData=ctx.getImageData(x,y,w,h)返回的是一維數組:[r1,g1,b1,a1,r2,g2,b2,a2...]
ctx.putImageData(imageData,x,y)把imageData放在(x,y)處
ctx.putImageData(imageData, x, y, dirtyX, dirtyY, dirtyW, dirtyH)只顯示(dirtyX,dirtyY)處的寬dirtyW,dirtyH的區域
Canvas陰影繪制
Canvas剪輯區域
Canvas繪制曲線
狐線:
context.arc(x,y,radius, starAngle,endAngle, anticlockwise)圓心(x,y) 半徑radius從starAngle到endAngleanticlockwise代表是否逆時針方向
生成工具
Canvas Quadratic Curve Example
http://blogs.sitepointstatic.com/examples/tech/canvas-curves/quadratic-curve.html
http://blogs.sitepointstatic.com/examples/tech/canvas-curves/bezier-curve.html
二次樣條曲線:
context.quadraticCurveTo(qcpx,qcpy, qx,qy)
貝塞爾曲線:
context.bezierCurveTo(cp1x,cp1y, cp2x, cp2y, x,y)
清除(x,y)點起, 寬width,高height的區域,用于重新繪制
離屏技術是什么:通過在離屏Canvas中繪制元素,再復制到顯示Canvas中,從而大幅提高性能的一種技術。
使用離屏技術:
離屏技術:
一個Canvas中的圖形繪制到另一個Canvas方法:
ctx.drawImage(canvas,x,y),該方法把canvas繪制在(x,y)處ctx.drawImage(canvas,x,y, w,h),該方法把canvas繪制在(x,y)處,并縮放為寬w,高hctx.drawImage(canvas, sx, sy, sw, sh, dx, dy, dw, dh),該方法把canvas中(sx, sy)處的寬sw,高sh的區域,繪制到(dx,dy)處,并縮放為寬dw, 高dh
對canvas插件的相關了解
什么是Canvas插件,掌握Chart.js插件,了解Chartist.js和HighCharts.js插件
(圖表)Chart.js插件:https://www.chartjs.org/
Chartist.js插件是一個簡單的響應式圖表插件:支持SVG格式(http://gionkunz.github.io/chartist-js/)
HighCharts.js插件:方便快捷的HTML5交互性圖標庫:https://www.highcharts.com/
Chartist.js插件與HighCharts.js插件
響應式布局,它的用戶體驗友好,響應式網站可以根據不同終端,不同尺寸和不同應用環境,自動調整界面布局,展示內容,提供非常好的視覺效果。響應式布局就是一個網站能夠兼容多個終端
示例:
<style>#canva { border: 1px solid red;}</style><div><canvas id="canva" width="200" height="200"></canvas>// 繪制寬高200的canvas</div>
在開始繪圖時,先要獲取Canvas元素的對象,在獲取一個繪圖的上下文。
獲取Canvas對象 ,使用document對象的getElementById()方法獲取。
var canvas=document.getElementById("canvas")可以使用通過標簽名稱來獲取對象的getElementsByTagName方法
使用getContext()方法來獲取
var context=canvas.getContext("2d")
context.font="98px 黑體"; // 文字樣式context.fillStyle="red"; // 文字顏色context.textAlign="center"; // 文字對齊方式// 繪制文字context.fillText("達達前端",100, 123, 234);
繪制圖像:
使用drawImage()方法可以將圖像添加到Canvas畫布中,繪制一幅圖像,需要有三個重載的方法:
使用:
drawImage(image, x, y) // 在畫布上定位圖像// 方法在畫布上繪制圖像、畫布或視頻。// 方法也能夠繪制圖像的某些部分,以及/或者增加或減少圖像的尺寸。drawImage(image, x, y, width, height)// 在畫布上定位圖像,并規定圖像的寬度和高度drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight)// 剪切圖像,并在畫布上定位被剪切的部分
參數:
參數描述image規定要使用的圖像,畫布或視頻sourceX開始剪切的x坐標位置sourceY開始剪切的y坐標位置sourceWidth被剪切圖像的寬度sourceHeight被剪切圖像的高度destX在畫布上放置圖像的 x 坐標位置destY在畫布上放置圖像的 y 坐標位置destWidth要使用的圖像的寬度destHeight要使用的圖像的高度
插入圖像:
function Draw() { // 獲取canvas對象 var canvas=document.getElementById("canvas"); // 獲取2d上下文繪圖對象 var context=canvas.getContext("2d"); // 使用Image()構造函數創建圖像對象 var newImg=new Image(); // 指定圖像的文件地址 newImg.src="../images/dadaqianduan.jpg"; newImg.onload=function () { // 左上角開始繪制圖像 context.drawImage(newImg, 0, 0); context.drawImage(newImg, 250, 100, 150, 200); context.drawImage(newImg, 90, 80, 100, 100, 0, 0, 120, 120); }}
在Canvas中繪制文字“達達前端”:
// canvas 寬高200<canvas id="canvas" width="200" height="200"></canvas><style type="text/css">canvas { border: 2px solid #ccc;}</style><script> // 獲取canvas var canvas=document.getElementById("canvas"); var context=canvas.getContext("2d"); // 設置字體 context.font="98px 黑體"; // 填充 context.fillStyle="#036"; // 文本水平位置 context.textAlign="center"; // 執行繪制 context.fillText("達達前端",100, 120, 200);</script>
兩個方法:
繪制矩形邊框,使用strokeStyle方法:
// 繪制矩形邊框strokeRect(x,y, width, height);
填充矩形區域,使用fillRect()方法:
// 填充矩形區域fillRect(x,y,width,height);
繪制矩形
// 繪制矩形function drawRect() { var canvas=document.getElementById("canvas"); var context=canvas.getContext("2d"); // 描邊 context.strokeStyle="#000"; // 線條寬度 context.lineWidth=1; // 矩形邊框 context.strokeRect(50,50, 150, 100); // 填充 context.fillStyle="#f90"; // 矩形 context.fillRect(50,50,150,100);}window.addEventListener("load",DrawRect,true);
使用clearRect方法,可以擦除指定的矩形區域:
// 擦除指定的矩形區域context.clearRect(x,y,width,height)
在實際開發中,畫布是默認300*150的大小。
示例:
// 為畫布設置邊框canvas { border: 1px solid #ccc;}// 準備畫布,默認是300*150// 設置畫布的大小<canvas width="1200" height="800"></canvas>// 準備繪制工具<script>// 獲取元素var myCanvas=document.querySelector('canvas');// 獲取上下文,繪制工具箱var ctx=myCanvas.getContext('2d');// 移動畫筆ctx.moveTo(100,100);// 繪制直線,軌跡ctx.lineTo(200,100);// 描邊ctx.stroke()
向 HTML5 頁面添加 canvas 元素
// 規定元素的 id、寬度和高度<canvas id="myCanvas" width="200" height="100"></canvas>
圖形繪制
需要理解些概念:
設置樣式
矩形繪制
圓弧繪制
繪制文本
繪制圖片
坐標變換
創建繪圖路徑
使用方法:beginPath()和closePath(),分別表示開始一個新的路徑和關閉當前的路徑
closePath方法關閉當前路徑
繪制網格,網格大小
var grid=10;// 畫多少條x軸方向的線,橫向的條數,畫布的高度var canvasHeight=myCanvas.heightvar canvasWidth=myCanvas.width// 畫布寬高ctx.canvas.widthctx.canvas.height// 網格大小var gridSize=10;var canvasHeight=ctx.canvas.height;var xLineTotal=canvasHeight / gridSize// 總線條var xLineTotal=Math.floor(canvasHeight / gridSize);for (var i=0; i<=xLineTotal; i++) { ctx.beginPath(); ctx.moveTo(0, i*gridSize-0.5); ctx.lineTo(canvasWidth, i*gridSize-0.5); ctx.strokeStyle='#eee'; ctx.stroke();}// 畫多少條y軸方向的線var yLineTotal=canvasWidth / gridSizevar yLineTotal=Math.floor(canvasWidth / gridSize);for (var i=0; i <=yLineTotal; i++) { ctx.beginPath(); ctx.moveTo(i*gridSize-0.5,0); ctx.lineTo(i*gridSize-0.5,canvasHeight); ctx.strokeStyle='#eee'; ctx.stroke();}
繪制坐標系,確定圓點,確定離畫布旁邊的距離,確定坐標軸的長度,確定箭頭的大小,繪制箭頭填充。
// 繪制坐標系var space=20;var arrowSize=10;// 畫布寬高var canvasWidth=ctx.canvas.width;var canvasHeight=ctx.canvas.height;// 坐標系var x0=space;var y0=canvasHeight - space;// 繪制x軸ctx.moveTo(x0,y0);ctx.lineTo(canvasWidth-space, y0);ctx.stroke();// 箭頭ctx.lineTo(canvasWidth-space-arrowSize, y0 + arrowSize/2);ctx.lineTo(canvasWidth-space-arrowSize, y0 - arrowSize/2);ctx.lineTo(canvasWidth-space, y0);ctx.fill();ctx.stroke();// 繪制y軸ctx.beginPath();ctx.moveTo(x0, y0);ctx.lineTo(space, space);ctx.stroke();// 箭頭ctx.lineTo(space+space-arrowSize/2, space + arrowSize);ctx.lineTo(space-space-arrowSize/2, space - arrowSize);ctx.lineTo(space, space);ctx.fill();ctx.stroke();// 繪制點var coordinate={ x: 146, y: 356}// 點尺寸var dottedSize=6;ctx.moveTo(coordinate.x - dottedSize/2, coordinate.y - dottedSize/2);ctx.lineTo(coordinate.x + dottedSize/2, coordinate.y - dottedSize/2);ctx.lineTo(coordinate.x + dottedSize/2, coordinate.y + dottedSize/2);ctx.lineTo(coordinate.x - dottedSize/2, coordinate.y + dottedSize/2);ctx.closePath();ctx.fill();
arc方法和rect方法
arc創建一個圓形,rect創建一個矩形,最后調用stroke()方法和fill()方法
// 圓形context.arc(100,100,30,0,Math.PI*2,true);
使用beginPath()方法可以新創建一個子路徑,closePath()方法用來閉合路徑的。
繪制兩條直線
function DrawLine() { var canvas=document.getElementById("canvas"); var context=canvas.getContext("2d"); // 創建繪制過程 context.beginPath(); context.moveTo(50,50); context.lineTo(120,120); context.lineTo(120,60); context.closePath(); context.strokeStyle="#000"; // 執行繪制 context.stroke();}
如果不用beginPath()方法,繪制圖形時不再創建子路徑,第一次的圖形在執行過程中會被繪制填充兩次。
圖形組合
屬性 globalCompositeOperation 設置如何在畫布上組合顏色
12中組合類型:
值說明copy只繪制新圖形,刪除其他所有內容darker在圖形重疊的地方,顏色由兩個顏色值相減后決定destination-atop已有的內容只在它和新的圖形重疊的地方保留,新圖形繪制在內容后destination-in在新圖形和已有畫布重疊的地方,已有內容都保留,所有其他內容成為透明destination-out在新圖形和已有內容不重疊的地方,已有內容保留所有其他內容成為透明destination-over新圖形繪制于已有內容的后面lighter在圖形重疊的地方,顏色由兩種顏色值的疊加值來決定source-atop只在新圖形和已有內容重疊的地方才繪制新圖形source-in在新圖形和已有內容重疊的地方,新圖形才會被繪制,所有其他內容成為透明source-out只在和已有圖形不重疊的地方繪制新圖形source-over新圖形繪制于已有圖形的頂部xor在重置和正常繪制的其他地方,圖形都成為透明
繪制曲線
// 圓形,曲線arc(x, y, radius, startAngle, endAngle, counterclockwise);
<style>// 畫布背景顏色#canvas { background: #000;}</style>// 畫布寬度400<canvas id="canvas" width="400" height="400"><script> var canvas=document.getElementById('canvas'); var context=canvas.getContext('2d') // 開始 context.beginPath(); // 繪制圓形 context.arc(100, 100, 50, 0, Math.PI*2, true); // 關閉 context.closePath(); // 填充顏色 context.fillStyle='rgb(255,255,255)'; context.fill();</script>
如果使用css設置寬高,畫布會按照300*150的比例進行縮放,將300*150的頁面顯示在400*400的容器中。
// 設置畫布寬度var cx=canvas.width=400;var cy=canvas.height=400;
使用js動態設置寬高。
建議使用HTML中的width和height,或者js動態設置寬高
創建一個canvas標簽,第一步:
// 獲取這個canvas的上下文對象var canvas=document.getElementById('canvas');var context=canvas.getContext('2d');
方法:
輔助線繪制弧線:arcTo() 方法
語法:
// 輔助線繪制弧線arcTo(x1, y1, x2, y2, radius)
arcTo()方法繪制一條弧線
代碼:
// 繪制一條弧線function draw() { var canvas=document.getElementById('canvas'); var context=canvas.getContext('2d'); // 開始繪制 context.beginPath(); // 移動點 context.moveTo(80, 120); // 繪制線條 context.lineTo(150, 60); context.lineTo(180, 130); // 描邊 context.strokeStyle="rgba(0,0,0,0.4)"; context.lineWidth=2; context.stroke(); context.beginPath(); context.moveTo(80,120); context.arcTo(150,60,180,130,50); context.strolkeStyle="rgba(255,135,0,1)"; context.stroke();}
quadraticCurveTo()方法:
quadraticCurveTo(cpX, cpY, x, y);// cpX, cpY描述了控制點的坐標,x, y描述了曲線的終點坐標
bezierCurveTo()方法:它是應用于二維圖形應用程序的數學曲線。
bezierCurveTo(cp1X, cp1Y, cp2X, cp2Y, x, y);// cp1X, cp1Y 表示第一個控制點的坐標 // cp2X, cp2Y 表示第二個控制點的坐標 // x, y表示曲線的終點坐標
繪制曲線:
function draw() { // 繪制曲線 var canvas=document..getElementById('canvas'); var context=canvas.getContext('2d'); // 開始繪制 context.beginPath(); // 移動 context.moveTo(100,180); // 連線 context.lineTo(110,80); context.moveTo(260,100); context.lineTo(300,200); // 描邊 context.strokeStyle="rgba(0,0,0,0.4)"; // 設置寬度 context.lineWidth=3; context.stroke(); context.beginPath(); context.moveTo(100,180); // 繪制貝濟埃曲線 context.bezierCurveTo(110,80,260,100,300,200); // 設置寬度 context.lineWidth=3; context.strokeStyle="rgba(255,135,0,1)"; context.stroke();}
var canvas=document.getElementById('canvas');var context=canvas.getContext('2d');// 畫布寬度200var canX=canvas.width=200var canY=canvas.height=200;// 開始繪制context.beginPath();// 四分之一圓context.arc(100, 100, 50, 0, Math.PI*0.5, false);context.strokeStyle="white"context.stroke();context.beginPath();context.lineTo(200, 200);context.lineTo(200, 100);context.lineTo(100,50);context.strokeStyle='#fff';context.stroke();
設置陰影,shadowBlur -context.shadowBlur=20
gradient.addColorStop(stop,color)
使用三種方法插入圖像
function draw() { var canvas=document.getElementById('canvas'); var context=canvas.getContext('2d'); // image實例 var newImg=new Image(); newImg.src='../images/dada.jpg' // 指定圖像的文件地址 newImg.onload=function(){ // 繪圖 context.drawImage(newImg, 0, 0); context.drawImage(newImg, 250,100, 150,200); context.drawImage(newImg, 90,80,100,100,0,0,120,120); }}
在插入圖像之前,需要考慮圖像加載的時間,如果圖像沒加載完成就已經執行drawImage()方法,就不會顯示任何圖片。
提供了兩種漸變的創建的方法:
// 創建線性漸變createLinearGradient()方法// 創建徑向漸變createRadialGradient()方法
設置漸變顏色和過渡方式
語法如下:
addColorStop(offset, color);
function draw() { var canvas=document.getElementById('canvas') var context=canvas.getContext('2d') // 創建漸變對象,線性漸變 var grd=context.createLinearGradient(0,0,300,0) // 設置漸變顏色 grd.addColorStop(0, '#xxx'); // 設置顏色 grd.addColorStop(1, '#xxx'); // 設置顏色 // 將填充樣式設置為線性漸變對象 context.fillStyle=grd; context.fillRect(0,0,300,80);}
function draw() { var canvas=document.getElementById('canvas') var context=canvas.getContext('2d') // 徑向漸變 var grd=context.createRadialGradient(50,50,0,100,100,90); // 設置漸變顏色以及方式 grd.addColorStop(0,'#xxx'); grd.addColorStop(1,'#xxx'); context.fillStyle=grd; context.beginPath(); // 圓形 context.arc(100,100,90,0,Math.PI*2,true); context.fill();}
線帽屬性:lineCap,表示指定線條的末端如何繪制值:lineCap: butt, round, square,當線條具有一定的寬度才能表現出來。
butt// 定義了線段沒有線帽round// 定義了線段的末端為一個半圓形的線帽square// 定義了線段的末端為一個矩形的線帽
線條的連接屬性lineJoin,用于兩條線條到的連接方式:
miter兩條線段的外邊緣一直延伸到它們相交,屬性miterLimit是用來描述如何繪制兩條線段的交點,是表示延伸長度和線條長度的比值。
默認為10,只有miter使用時有效
lineJoin=[value];round // 兩條線段的外邊緣應該和一個填充的弧結合bevel // 兩條線段的外邊緣應該和一個填充的三角形相交
語法如下:
createPattern(image, repetitionStyle)
function draw() { var canvas=document.getElementById('canvas') var context=canvas.getContext('2d') var img=new Image(); // 使用Image()構造函數創建圖像對象 img.src='../images/xxx' // 指定圖像的文件地址 img.onload=function() { // 繪圖模式 var ptrn=context.createPattern(img, 'repeat'); // 填充樣式 context.fillStyle=ptrn; // 填充矩形 context.fillReat(0,0,500,200); }}
移動變化:
// 移動translate(dx,dy);// 繪制function draw() { var canvas=document.getElementById('canvas') var context=canvas.getContext('2d') // 設置移動偏移量 context.translate(200, 200); // 繪制一個圓形 ArcFace(context);}// 繪制一個圓形function ArcFace(context) { // 繪制一個圓形邊框 context.beginPath(); // 繪制圓形 context.arc(0,0,90,0,Math.PI*2,true); // 線寬 context.lineWidth=5; // 描邊 context.strokeStyle='#f90'; context.stroke(); // 繪制 context.beginPath(); context.moveTo(-30, -30); context.lineTo(-30, -20); context.moveTo(30, -30); context.lineTo(30, -20); context.moveTo(-20, 30); // 曲線 context.bezierCurveTo(-20, 44, 20, 30, 30, 20); context.strokeStyle='#000'; context.lineWidth=10; context.lineCap='round'; // 笑臉 context.stroke();}
縮放變換,語法如下:
scale(sx, sy);// sx為水平方向上的縮放因子,sy為垂直方向上的縮放因子
// 示例function draw() { var canvas=document.getElementById('canvas') var context=canvas.getContent('2d') // 移動 context.translate(200,200); // 縮放 context.scale(0.5,0.5); ArcFace(context);}
旋轉變換:
rotate(angle)
// 旋轉例子function draw() { var canvas=document.getElementById('canvas'); var context=canvas.getContext('2d') context.translate(200,200); // 旋轉 context.rotate(Math.PI/6); context.scale(0.5, 0.5) ArcFace(context)}
矩形變形,語法如下:
transform(m1x,m1y,m2x,m2y,dx,dy); // 移動,縮放,旋轉1. 移動translate (dx, dy)2. 縮放scale (sx,sy)3. 旋轉rotate (A)
*請認真填寫需求信息,我們會在24小時內與您取得聯系。