可能對(duì)于一些人來(lái)說(shuō),流利說(shuō)是一份工作,而對(duì)于我來(lái)說(shuō),流利說(shuō)卻是一段深刻的旅程,改變了我的生活,也塑造了我的性情、人格。從 2013 年作為第 7 號(hào)員工加入流利說(shuō),為之效力6年,從最早期民房創(chuàng)業(yè),到納斯達(dá)克 IPO,這段經(jīng)歷,我自認(rèn)為頗具一個(gè)理想主義者的傳奇色彩,有些故事,是我愿意,也值得分享的。
老王(流利說(shuō) CEO 王翌)以前總借他恩師的話(huà)說(shuō),人這一輩子能做一兩件漂亮事就不錯(cuò)了。我覺(jué)得,流利說(shuō),算我過(guò)去做的一件漂亮事。以前,我也常和同事說(shuō),如果你現(xiàn)在一切的經(jīng)歷,在日后不足以用故事說(shuō)與人聽(tīng),可能你現(xiàn)在經(jīng)歷的還不夠痛,你還不夠刻苦。所幸,過(guò)去的經(jīng)歷,給我留下了幾個(gè)故事。
這一次,我想分享當(dāng)初選擇加入流利說(shuō)的故事,分享這個(gè)過(guò)程中我的思考、行動(dòng)。一方面,單純的想記錄這段故事。因?yàn)殡S著年歲增加,記性卻是退減的。文字是最好的保存記憶的方式;另一方面,常常遇到設(shè)計(jì)師朋友們聊如何選擇工作機(jī)會(huì),遇到創(chuàng)業(yè)的邀約機(jī)會(huì)怎么判斷、決策的問(wèn)題,我希望這個(gè)故事,能給遇到此類(lèi)問(wèn)題的朋友一些啟發(fā)。
好,聽(tīng)故事吧。
2012 年,我退出了聯(lián)合創(chuàng)立一年半的公司。而在一年半以前,我從阿里云公司離開(kāi),放棄了一筆可觀(guān)的 RSU 股票,當(dāng)時(shí),我是阿里云最早的 28 位設(shè)計(jì)師之一。結(jié)束創(chuàng)業(yè)后,2012 年 11 月 14 日 19:17,我發(fā)了一條微博,表示想看看新的機(jī)會(huì)。這條微博,有 16 個(gè)轉(zhuǎn)發(fā)。其中有 1 個(gè)轉(zhuǎn)發(fā),被流利說(shuō)聯(lián)合創(chuàng)始人 Ben 看到,他把這條微博轉(zhuǎn)發(fā)給了聯(lián)合創(chuàng)始人王翌(流利說(shuō) CEO)。
在此之前,我和 Ben、王翌素不相識(shí),網(wǎng)友都談不上。Ben 之所以看到那條轉(zhuǎn)發(fā),是因?yàn)樗P(guān)注好友里面,有一位正是我一款瀏覽器插件產(chǎn)品 – 微博急簡(jiǎn) 的用戶(hù)。
△ 使用「微博急簡(jiǎn)」前后,微博主頁(yè)的對(duì)比
容我多說(shuō)幾句,介紹一下微博急簡(jiǎn)這款產(chǎn)品。2011 年左右,新浪微博的使用體驗(yàn),非常糟糕,逐步商業(yè)化帶來(lái)的各種廣告,讓原本不好的體驗(yàn),變得更加讓我無(wú)法容忍。很快,一個(gè)叫 stylish 的 Chrome 插件工具,在微博設(shè)計(jì)/技術(shù)圈子里流傳。用戶(hù)安裝 stylish 插件后,通過(guò)修改 CSS 來(lái)定制自己的微博體驗(yàn)。同時(shí),你還可以把 CSS 分享給其他用戶(hù)。
這太好玩了!我按自己的使用喜好,給自己訂制了一套極簡(jiǎn)體驗(yàn)的新浪微博。玩了幾天后,我決定做一個(gè)改善微博體驗(yàn)的瀏覽器插件。為什么要重新做一個(gè)插件呢?我認(rèn)為,stylish 的使用門(mén)檻、操作成本都太高,僅僅是專(zhuān)業(yè)人士的玩具。我希望普通人,也可以通過(guò)一鍵安裝瀏覽器插件,獲得舒服的微博體驗(yàn)。因此,我給插件取名 – 微博急簡(jiǎn)??吹贸鰜?lái),我是多么急迫的想簡(jiǎn)化微博的體驗(yàn)。
兩周后,微博急簡(jiǎn)就上線(xiàn)了。幾個(gè)版本后,體驗(yàn)就趨于穩(wěn)定。高峰的時(shí)候,有 10 萬(wàn)左右的用戶(hù)使用,相關(guān)微博話(huà)題有 600 萬(wàn)。有很多行業(yè)內(nèi)的大咖成為我的用戶(hù),像馮大輝、少楠、方軍等。獲得了很多用戶(hù)的好評(píng),最讓我嘚瑟的是現(xiàn)任丁香園產(chǎn)品總監(jiān)少楠的評(píng)價(jià):用一個(gè)插件秒殺了新浪UED團(tuán)隊(duì)。
通過(guò)微博急簡(jiǎn)這個(gè)產(chǎn)品,我想分享以下幾點(diǎn):
這三點(diǎn),既是我的觀(guān)點(diǎn),也是我的特質(zhì)。正是因?yàn)檫@樣的特質(zhì),才遇到后面加入流利說(shuō)的契機(jī)。
回到故事主線(xiàn)。
我發(fā)完那條微博后的 1 小時(shí),王翌就給了我微博私信。老實(shí)說(shuō),今天是我第一次注意到這個(gè)「相隔 1 小時(shí)」的時(shí)間細(xì)節(jié)。王翌的行動(dòng)力、執(zhí)行力,對(duì)于人才的執(zhí)著追求,著實(shí)讓我佩服。這不僅僅是對(duì)我,對(duì)流利說(shuō)早期的員工,以及后面的核心員工,都是如此。
他的消息里,有三個(gè)關(guān)鍵詞:exciting、團(tuán)隊(duì)成立 2 個(gè)月、移動(dòng)教育。對(duì)于這條消息,我禮貌的回應(yīng),但內(nèi)心其實(shí)是「呵呵」的。即使,我看到他 LinkedIn 主頁(yè)上 Google PM 的經(jīng)歷,很亮眼。呵呵的原因是,這個(gè)公司太早期了,才兩個(gè)月。2013 年,移動(dòng)教育是啥東西?而且,創(chuàng)始人還這么不務(wù)實(shí),動(dòng)不動(dòng)就標(biāo)榜是一個(gè) exciting 的機(jī)會(huì)。
雖然,王翌后續(xù)一直聯(lián)系我,但我基本是忽略的狀態(tài)。期間,我短暫的加入了一個(gè)朋友的創(chuàng)業(yè)公司。2013 年 1 月底,春節(jié)前的幾天,我再次收到王翌的私信:流利說(shuō) App 即將上架,想約我再聊一聊。于是,我們約在文二西路白鴉(有贊創(chuàng)始人)的貝塔咖啡。
?
△ 貝塔咖啡館
那天晚上,王翌給我展示了流利說(shuō)最早的 App,程序其實(shí)還不太穩(wěn)定。但他仍舊極具信心的表達(dá)了對(duì)于語(yǔ)音互動(dòng)的看好,以及發(fā)出盛情邀請(qǐng)。我對(duì)這個(gè) App 的產(chǎn)品與交互的第一印象,是好的,但對(duì)于王翌的第一印象卻是復(fù)雜的,既覺(jué)得這人有激情、有想法,同時(shí)又覺(jué)得不太靠得住,夸夸其談,他太會(huì)說(shuō)了。
深色模式該從何處著手設(shè)計(jì)又要考慮哪些因素?一起來(lái)看看~
其實(shí)回顧我們常用的APP,有很多都更新了深色模式,而且每個(gè)APP對(duì)深色的定義和設(shè)計(jì)都有差異。
實(shí)際上深色模式已經(jīng)來(lái)臨,而且在很多產(chǎn)品中都能發(fā)現(xiàn)它的身影,之后也會(huì)愈加流行。那么設(shè)計(jì)師面對(duì)深色模式,該從何處著手設(shè)計(jì)又要考慮哪些因素呢?
本文就為大家提供一份全面的總結(jié)。文章目錄如下:
1. 需求趨勢(shì)
過(guò)去一年以來(lái),Android 10和iOS 13上都適配了深色模式,而且Apple和Google也一直致力于將資源和注意力投入到深色模式中,這也讓深色模式備受用戶(hù)的關(guān)注。
2. 專(zhuān)注內(nèi)容
深色模式在弱光環(huán)境下具有更好的可讀性,讓我們更專(zhuān)注于眼前的屏幕。同時(shí)深色的背景會(huì)降低內(nèi)容周?chē)氐挠绊?,特別是以圖片和視頻為主的應(yīng)用,讓用戶(hù)更專(zhuān)注于內(nèi)容。
作為內(nèi)容消費(fèi)型應(yīng)用的Netflix ,把深色背景作為默認(rèn)設(shè)計(jì)樣式,深色的設(shè)計(jì)讓用戶(hù)更能集中注意力,延長(zhǎng)使用時(shí)間。
3. 減輕刺激
相對(duì)于其他顏色,深色系的設(shè)計(jì)在夜晚看著最舒服。可能晚上玩手機(jī)不用擔(dān)心光線(xiàn)太刺眼,但是深色模式對(duì)護(hù)眼并沒(méi)有什么幫助,只能說(shuō)可以減少對(duì)眼睛的刺激。
4. 提高續(xù)航
深色模式更省電只適用于OLED屏幕。OLED面板的每個(gè)像素點(diǎn)可以單獨(dú)發(fā)光,當(dāng)使用深色模式時(shí),部分像素點(diǎn)被熄滅,只點(diǎn)亮部分像素,屏幕的一部分相當(dāng)于處在休眠狀態(tài),所以會(huì)更加省電。
在深色模式下,Apple重新審視了iOS中UI樣式和顏色的含義,讓我們來(lái)看看在iOS上設(shè)計(jì)深色模式帶來(lái)的變化。
語(yǔ)義化顏色(Semantic Colors)
所謂語(yǔ)義化顏色,就是不再通過(guò)某一固定的RGB色值來(lái)描述顏色,而是根據(jù)用途來(lái)描述,讓界面元素可以自動(dòng)適配當(dāng)前的外觀(guān)模式。
淘寶團(tuán)隊(duì)就參考了蘋(píng)果官方的適配建議,通過(guò)語(yǔ)義化顏色的方式進(jìn)行適配,使適配成本大幅降低。設(shè)計(jì)師根據(jù)不同UI元素的特性先期制定顏色語(yǔ)義化規(guī)則,進(jìn)而技術(shù)在框架層面通過(guò)「顏色自動(dòng)反轉(zhuǎn)」技術(shù)實(shí)現(xiàn)顏色反轉(zhuǎn)。
系統(tǒng)顏色
除了語(yǔ)義化顏色,Apple還提供了9種預(yù)定義的系統(tǒng)顏色,在淺色和深色模式中,這些顏色會(huì)動(dòng)態(tài)變化,支持整個(gè)系統(tǒng)的外觀(guān),同樣也可以自適應(yīng)選定的界面樣式。
模糊與動(dòng)態(tài)效果
在iOS13上,蘋(píng)果引入了4種模糊效果和8種動(dòng)態(tài)效果,它們自動(dòng)適應(yīng)iOS界面樣式。這是在淺色和深色模式下不同的模糊效果。
蘋(píng)果還在iOS深色模式排版套件中引入4種動(dòng)態(tài)效果,其中3種為疊加效果,1種分隔效果。
谷歌提供了廣泛的文檔支持,幫助設(shè)計(jì)師了解深色主題如何在A(yíng)ndroid生態(tài)系統(tǒng)中運(yùn)行。
Elevation(陰影)
UI界面元素間的投影最能讓用戶(hù)清晰地感知用戶(hù)界面的深度。在設(shè)計(jì)深色主題時(shí),組件將保留與淺色主題相同的默認(rèn)陰影組件。Elevation越靠上, 顏色就會(huì)越淺。
無(wú)障礙性與對(duì)比
深色UI設(shè)計(jì)中的背景應(yīng)足夠暗以顯示白色文本。設(shè)計(jì)師要注意背景和文字之間至少使用15.8:1的對(duì)比度。這樣可以確保將正文放在最前面時(shí),能通過(guò)WCAG(Web內(nèi)容無(wú)障礙指南,使網(wǎng)站內(nèi)容更容易訪(fǎng)問(wèn))的AA標(biāo)準(zhǔn)。
顏色
深色模式必須避免飽和的顏色,以免引起眼睛疲勞。相反,設(shè)計(jì)師應(yīng)專(zhuān)注于使用不飽和的顏色,以增加清晰度。主色和輔色的選擇還取決于對(duì)淺色和深色UI主題的考慮。
文字不透明度
在深色背景上設(shè)計(jì)淺色文字時(shí),高度強(qiáng)調(diào)的文字不透明度為87%;一般提示文字的不透明度為60%;禁用文字的不透明度為38%。
蘋(píng)果和谷歌都利用各自的設(shè)計(jì)原則,為深色模式設(shè)計(jì)做準(zhǔn)備工作。在實(shí)際設(shè)計(jì)過(guò)程中,不單需要這些基本原則,更重要的是要注意設(shè)計(jì)深色模式的實(shí)用要點(diǎn)。
設(shè)計(jì)深色背景時(shí)不是簡(jiǎn)單的把白變成黑,而是對(duì)背景使用比較暗的色調(diào),以減少眼睛疲勞。
在淺色模式中,我們傾向于用細(xì)微的陰影來(lái)傳達(dá)界面深度,使用起來(lái)更加自然。但是在大多數(shù)深色模式下,陰影的效果并不明顯,通常用顏色的深淺來(lái)傳達(dá)界面的層級(jí)關(guān)系。
關(guān)鍵點(diǎn):注意應(yīng)用場(chǎng)景
在知乎的深色模式中,背景的設(shè)計(jì)從深到淺使用了三級(jí)灰度,讓頁(yè)面的層級(jí)更分明。
一級(jí)灰度的應(yīng)用場(chǎng)景主要是大的背景色,使用面積相對(duì)比較大顏色最深;二級(jí)灰度的應(yīng)用場(chǎng)景是選項(xiàng)的背景色,根據(jù)選項(xiàng)的數(shù)量設(shè)置使用面積,位置排布比較靈活;三級(jí)灰度的顏色最淺,使用面積最小,通常用在分割線(xiàn)中。
白底黑字和黑底白字帶給我們的用眼體驗(yàn)是不一樣的。設(shè)計(jì)不當(dāng)?shù)纳钌J匠3R驗(yàn)閺?qiáng)對(duì)比而變得很刺眼,同時(shí)為了提高對(duì)光線(xiàn)的吸收虹膜會(huì)張得更開(kāi),更容易造成眼部疲勞。
關(guān)鍵點(diǎn):文字間的對(duì)比
深色模式中,文字的用色通常是純灰色,不同位置的文字例如標(biāo)題、正文和注釋使用深淺不同的顏色作對(duì)比。上圖是深色的微信,就利用這種方法來(lái)區(qū)分不同文字內(nèi)容,展示文字層次關(guān)系。
另外每個(gè)應(yīng)用的定位都不一樣,界面中想傳達(dá)的信息也有差異,所以要注意不同的設(shè)計(jì)思路。
關(guān)鍵點(diǎn):文字與背景的對(duì)比
已經(jīng)更新深色模式的應(yīng)用主要分為兩大類(lèi),一類(lèi)屬于工具型應(yīng)用例如QQ、微信、百度網(wǎng)盤(pán)等,這類(lèi)應(yīng)用追求的是信息的有效傳達(dá),在設(shè)計(jì)時(shí)文字內(nèi)容和背景色的區(qū)分比較明顯。
上圖是百度網(wǎng)盤(pán)的深色模式,可以看出來(lái)標(biāo)題文字與背景有很明顯的對(duì)比,保障了用戶(hù)使用時(shí)的可操作性和易讀性。
這樣的設(shè)計(jì)不需要用戶(hù)過(guò)于沉浸式的閱讀,只需要幫助用戶(hù)快速找到有用的信息并方便使用,這是工具型應(yīng)用在設(shè)計(jì)深色模式時(shí)必備的原則。
另一類(lèi)屬于內(nèi)容型應(yīng)用例如知乎、簡(jiǎn)書(shū)等,這些應(yīng)用更注重沉浸式的閱讀體驗(yàn),因?yàn)橛脩?hù)通常會(huì)在某個(gè)界面中停留很久來(lái)查看內(nèi)容,所以需要文字與背景的低對(duì)比度為閱讀營(yíng)造柔和的氛圍。
簡(jiǎn)書(shū)的深色模式中,文字與背景的對(duì)比關(guān)系就設(shè)計(jì)得很弱,整個(gè)界面呈現(xiàn)出灰色調(diào),這樣的設(shè)計(jì)有助于在弱光環(huán)境下的長(zhǎng)時(shí)間閱讀和瀏覽。
深色模式應(yīng)該避免使用特別鮮艷的顏色,較高的明度和飽和度會(huì)與深色背景形成強(qiáng)烈的對(duì)比,讓頁(yè)面的可讀性變差并加深刺激。
關(guān)鍵點(diǎn):降低色彩明度
在由淺變深的過(guò)程中,知乎對(duì)改變了界面中所有圖標(biāo)的顏色。界面里面的圖標(biāo)和主題按鈕的色彩,在色相、飽和度上都沒(méi)有變化,但是明度被不同程度的降低,保證了在不同光照條件下的內(nèi)容的可讀性。
這是深色模式中處理色彩的一種方法,雖然在淺色界面中,我們更喜歡鮮艷的顏色,但明度低的顏色更適合深色主題。匹配這兩個(gè)模式另一個(gè)比較好的方法是創(chuàng)建互補(bǔ)的色板。
無(wú)論深色或者淺色,都只是產(chǎn)品向用戶(hù)呈現(xiàn)的一種界面狀態(tài),最終的目的是為了更良好的使用體驗(yàn)。
不管選擇什么樣的模式,都要記得從產(chǎn)品自身出發(fā),并牢記這幾點(diǎn):
文章來(lái)源:優(yōu)設(shè) 作者:Clip設(shè)計(jì)夾
藍(lán)藍(lán)設(shè)計(jì) 魏華寫(xiě)
在承擔(dān)ui設(shè)計(jì)項(xiàng)目中,常常碰到一些客戶(hù)不給設(shè)計(jì)師看己往的軟件或本公司設(shè)計(jì)師設(shè)計(jì)但沒(méi)有被客戶(hù)認(rèn)可的設(shè)計(jì)方案,怕限制設(shè)計(jì)師的思路。那究竟給設(shè)計(jì)師參考會(huì)不會(huì)限制設(shè)計(jì)師的思維呢?
實(shí)際上不會(huì)的,這是更多維度了解客戶(hù)的思考和角度,排除己被嘗試過(guò)的選項(xiàng)。
在進(jìn)行一個(gè)ui 設(shè)計(jì)前,深度方面應(yīng)該要了解業(yè)務(wù),用戶(hù)角色,環(huán)境生態(tài),交互流程,關(guān)鍵功能,核心價(jià)值,用戶(hù)體驗(yàn)的峰值點(diǎn)和難點(diǎn)在哪里…….總之了解的越深越好。
寬度方面,要看到競(jìng)品分析,行業(yè)趨勢(shì),國(guó)內(nèi)外優(yōu)秀案例欣賞,專(zhuān)業(yè)文章觀(guān)點(diǎn),應(yīng)該是可以拓展設(shè)計(jì)師的思維。
設(shè)計(jì)最核心的目標(biāo)應(yīng)該在于解決問(wèn)題,而不是單純的讓界面好看。
優(yōu)秀的設(shè)計(jì)師要明確解決問(wèn)題的目標(biāo),博采眾長(zhǎng),獨(dú)立思考,看眾多的競(jìng)品是看眾多的解題思路,多方位角度看問(wèn)題。各產(chǎn)品資源,核心技術(shù)不同,取舍不同,理解信息架框,運(yùn)營(yíng)思路,用戶(hù)特征,技術(shù)實(shí)現(xiàn)可能性,不會(huì)只照著您給的資料比貓畫(huà)虎。
因此,我認(rèn)為,放心的給設(shè)計(jì)師參考資料吧,互相的了解,溝通越多,越容易出好的作品。
這五張圖是最近藍(lán)藍(lán)設(shè)計(jì)的稿件,展現(xiàn)從一個(gè)模糊的概念性需求到一個(gè)可視化概念性方案的設(shè)計(jì)過(guò)程。
關(guān)于循環(huán)設(shè)計(jì),可持續(xù)發(fā)展是商業(yè)領(lǐng)域非常關(guān)注的話(huà)題,作為UX需提前轉(zhuǎn)變思維,給企業(yè)帶來(lái)更多價(jià)值,一線(xiàn)大廠(chǎng)已在運(yùn)用這種思維
本文共 3589 字,預(yù)計(jì)閱讀 10 分鐘
譯者推薦|本文從“可持續(xù)”和“設(shè)計(jì)”的兩點(diǎn)談起,來(lái)論述從線(xiàn)性經(jīng)濟(jì)向可持續(xù)經(jīng)濟(jì)的轉(zhuǎn)變,以及“可持續(xù)設(shè)計(jì)”的四個(gè)主要階段:理解、定義、制造、發(fā)布。
“循環(huán)設(shè)計(jì)”不是為了追求時(shí)髦或者抬升設(shè)計(jì)地位,而是將這個(gè)已經(jīng)日益庸俗化的“設(shè)計(jì)”冠為自己的定語(yǔ),是設(shè)計(jì)本身發(fā)展所趨,以及可持續(xù)發(fā)展所需,設(shè)計(jì)界需要對(duì)自己的責(zé)任有所承擔(dān),形成一個(gè)全局觀(guān)、系統(tǒng)性看待設(shè)計(jì)問(wèn)題的方式。讓回收利用和可持續(xù)發(fā)展成為一種規(guī)范,從而做到一勞永逸。
我們生活在一個(gè)呼喚變革的世界。在過(guò)去的50年中,現(xiàn)代社會(huì)所依賴(lài)的漫不經(jīng)心和無(wú)休止的消費(fèi)是不可持續(xù)的。我們從小就不關(guān)心自己的事情。如果有什么東西壞了,我們也就不修了。產(chǎn)品被設(shè)計(jì)成用完直接丟棄,而不是去修復(fù)。數(shù)字產(chǎn)品也不例外。然而,為了解決這些問(wèn)題,出現(xiàn)了一種新的思維方式:循環(huán)設(shè)計(jì)(可持續(xù)設(shè)計(jì))①。(益達(dá)說(shuō):其實(shí)這個(gè)理念和風(fēng)格已經(jīng)存在了很長(zhǎng)的時(shí)間,大多應(yīng)用在不為大眾所知的能源、材料再生流程之中,然而隨著時(shí)代的發(fā)展,循環(huán)設(shè)計(jì)可以變得更加普世。)
①注:循環(huán)設(shè)計(jì)是20世紀(jì)80-90年代產(chǎn)生的一種設(shè)計(jì)風(fēng)格,他又稱(chēng)回收設(shè)計(jì),是指實(shí)現(xiàn)廣義收回和利用的方法,即在進(jìn)行產(chǎn)品設(shè)計(jì)時(shí),充分考慮產(chǎn)品零部件及材料回收的可能性,回收價(jià)值的大致方法,回收處理結(jié)構(gòu)工藝性等于與回收有關(guān)的一系列問(wèn)題,以達(dá)到零部件及材料資源和能源的再利用。它旨在通過(guò)設(shè)計(jì)來(lái)節(jié)約能源和材料,減少對(duì)環(huán)境的污染,使人類(lèi)的設(shè)計(jì)物能多次反復(fù)利用,形成產(chǎn)品設(shè)計(jì)和使用的良性循環(huán)。
那么,循環(huán)設(shè)計(jì)方法意味著什么?在數(shù)字產(chǎn)品上要如何使用?在回答這些問(wèn)題之前,首先,我們需要仔細(xì)觀(guān)察我們是如何構(gòu)建我們的世界,為什么這個(gè)世界已經(jīng)不可持續(xù)了,并且要理解環(huán)保世界的需求是如何改變我們的思維方式,促使我們渴望從線(xiàn)性設(shè)計(jì)模型轉(zhuǎn)變?yōu)檠h(huán)設(shè)計(jì)模型。
向循環(huán)轉(zhuǎn)變
我們的經(jīng)濟(jì)主要基于“按需配置”流程之上。在此線(xiàn)性系統(tǒng)中,我們構(gòu)建了會(huì)在一段時(shí)間后淘汰的產(chǎn)品,并且將其組件視為垃圾。與此相反,循環(huán)設(shè)計(jì)方法將產(chǎn)品的生命周期視為一個(gè)閉環(huán),其中資源不斷地被重新利用。
在“經(jīng)典”線(xiàn)性模型中,產(chǎn)品經(jīng)歷了生產(chǎn)、消費(fèi)和破壞的各個(gè)階段,最終以浪費(fèi)告終。在設(shè)計(jì)一款循環(huán)產(chǎn)品過(guò)程中,我們使用的方法包含四大階段,這四個(gè)階段形成了一個(gè)閉環(huán),并形成了一個(gè)恒定的循環(huán),在這個(gè)循環(huán)中,不僅僅只有閃閃發(fā)亮的、新的,未使用過(guò)的材料才被受歡迎。
循環(huán)設(shè)計(jì)方法的四個(gè)階段是:
理解 / 定義 / 制造 / 發(fā)布
當(dāng)我們同時(shí)看線(xiàn)性設(shè)計(jì)和循環(huán)設(shè)計(jì)模型方法時(shí),有一點(diǎn)吸引人的是,開(kāi)始設(shè)計(jì)東西的時(shí)候,方法的差異。從只是生產(chǎn)某種東西,到對(duì)我們將要生產(chǎn)的產(chǎn)品做出深思熟慮的決定,以及在實(shí)施過(guò)程中付出的努力和關(guān)心,這是一個(gè)大轉(zhuǎn)變。
看看我們現(xiàn)在的立場(chǎng)
為什么做出這種轉(zhuǎn)變?nèi)绱说闹匾??我確信每個(gè)看新聞的人都聽(tīng)說(shuō)過(guò)氣候變化。NASA 致力于解決環(huán)境問(wèn)題,因此我們都可以非常詳細(xì)地了解人類(lèi)行為和無(wú)限增長(zhǎng)對(duì)我們生態(tài)系統(tǒng)的影響。
但好消息是我們不必繼續(xù)這樣做,因?yàn)槲覀兛梢院苋菀讖臄?shù)字世界中“產(chǎn)生”方式中學(xué)習(xí)事物的產(chǎn)生。電力廢棄物已成為現(xiàn)代世界的主要廢棄物來(lái)源之一。大量的手機(jī)和電腦被扔掉,隨之經(jīng)濟(jì)是建立在每年都有新東西的基礎(chǔ)上的。
當(dāng)您的手機(jī)屏幕意外碎裂時(shí),我們?cè)撛趺崔k?我們知道如何處理它嗎?我們知道如何修理嗎?我們并不知道……但是幸運(yùn)的是,有些設(shè)計(jì)師對(duì)此問(wèn)題提出了解決方案。Fairphone② 是一種合乎情理,模塊化的智能手機(jī),其組件數(shù)量很少,可以輕松更換和回收。大公司也應(yīng)朝這個(gè)方向邁出一步,讓回收利用和可持續(xù)發(fā)展成為一種時(shí)尚和規(guī)范,一勞永逸。
② Fairphone:這家公司生產(chǎn)的手機(jī)希望實(shí)現(xiàn)全球手機(jī)供應(yīng)鏈的公平貿(mào)易,具體而言就是不使用“沖突礦物”并且確保生產(chǎn)手機(jī)的工人沒(méi)有被奴役和壓榨,目前仍然堅(jiān)持在非洲貧困和戰(zhàn)亂的國(guó)家進(jìn)口材料,已經(jīng)在剛果和盧旺達(dá)境內(nèi)找到了一些礦山,用更好的商業(yè)實(shí)踐推動(dòng)當(dāng)?shù)亟?jīng)濟(jì)更健康地發(fā)展。
設(shè)計(jì)和設(shè)計(jì)師的重要性
設(shè)計(jì)師,比任何其他專(zhuān)業(yè)人士,都更有可能在一轉(zhuǎn)變中產(chǎn)生巨大的影響的人。我還敢說(shuō),我們有責(zé)任采用可持續(xù)設(shè)計(jì)的方式行動(dòng)和思考。因?yàn)槭俏覀儎?chuàng)造了那些最終出現(xiàn)在傳送帶上的東西。我們也有責(zé)任教育我們的用戶(hù)。幸運(yùn)的是,越來(lái)越多的人重視具有可持續(xù)發(fā)展目標(biāo)的產(chǎn)品或品牌,或者重視起在產(chǎn)品背后有意義的故事。同樣,可持續(xù)發(fā)展不僅成為流行語(yǔ),而且成為一種價(jià)值觀(guān),被越來(lái)越多的人意識(shí)到基于有限資源的無(wú)限增長(zhǎng)是無(wú)法實(shí)現(xiàn)的目標(biāo)。但是,要從線(xiàn)性經(jīng)濟(jì)向可持續(xù)經(jīng)濟(jì)轉(zhuǎn)變,我們需要學(xué)習(xí)不同的思維方式。幸運(yùn)的是,智能設(shè)備和數(shù)字產(chǎn)品的時(shí)代帶來(lái)了一種復(fù)雜的設(shè)計(jì)思維方法,可以作為物理世界中生產(chǎn)鏈的范例。
用戶(hù)體驗(yàn)必須提供什么
地球上有一個(gè)地方,您不能隨便丟東西:互聯(lián)網(wǎng)。這是一個(gè)對(duì)已有產(chǎn)品進(jìn)行再構(gòu)思的地方,您只能去完善它,不能丟棄它,因?yàn)槿绻灰怪g說(shuō):“我不喜歡我的網(wǎng)站,明天我將推出一個(gè)全新的網(wǎng)站”,那您便會(huì)失去用戶(hù)。
如果我們看一下可持續(xù)發(fā)展設(shè)計(jì)方法的四個(gè)主要階段,就會(huì)發(fā)現(xiàn)我們?cè)谟脩?hù)體驗(yàn)設(shè)計(jì)中使用的方法與此很相似。
讓我們?cè)俅慰匆幌滤膫€(gè)階段,然后將其更詳細(xì)地分解:
當(dāng)我們談?wù)撆c循環(huán)設(shè)計(jì)相關(guān)的理解時(shí),我們談?wù)摰氖窃陂_(kāi)始設(shè)計(jì)一個(gè)未來(lái)的產(chǎn)品之前就了解它的用戶(hù)和環(huán)境。研究一直是數(shù)字產(chǎn)品設(shè)計(jì)的基礎(chǔ)。與數(shù)字產(chǎn)品的連接比與實(shí)體產(chǎn)品的連接要更多的涉及到人類(lèi)的心理。因此不可避免地要開(kāi)發(fā)出新的研究方法,以幫助我們洞察用戶(hù)在使用某種產(chǎn)品時(shí)的想法、感受和行為。但這不僅與用戶(hù)有關(guān), 研究還必須深入到經(jīng)濟(jì)領(lǐng)域,并研究未來(lái)產(chǎn)品的組成部分,同時(shí)牢記它們必須可被再次利用。
在此階段,將定義(商業(yè))目標(biāo),并構(gòu)建一個(gè)商業(yè)模型畫(huà)布作為生產(chǎn)過(guò)程的計(jì)劃。用戶(hù)體驗(yàn)使用這種方法已有一段時(shí)間了,讓涉眾參與其中,并在設(shè)計(jì)過(guò)程中更多地激活它們。為我們?cè)O(shè)計(jì)的產(chǎn)品設(shè)定一個(gè)目標(biāo)是至關(guān)重要的,因?yàn)橛辛怂?,我們可以為用?hù)創(chuàng)造額外的價(jià)值。因此,無(wú)論是制作商業(yè)模型畫(huà)布還是舉辦精彩的價(jià)值主張研討會(huì),在生產(chǎn)方式中實(shí)施這些方法都會(huì)對(duì)當(dāng)前的生產(chǎn)流程產(chǎn)生巨大的影響。
這是關(guān)鍵部分?,F(xiàn)在我們正在做的事情就好像沒(méi)有明天一樣。隨著每種無(wú)法回收的產(chǎn)品的出現(xiàn),我們產(chǎn)生的廢料越來(lái)越多。但是循環(huán)方法是為產(chǎn)品創(chuàng)建一個(gè)原型,并定義將需要使用那些材料反映在產(chǎn)品原型上,并在定義階段概述的商業(yè)模型上定義材料。原型設(shè)計(jì)和構(gòu)思是用戶(hù)體驗(yàn)設(shè)計(jì)過(guò)程中的關(guān)鍵要素,這也是為什么需要制作原型。
根據(jù)循環(huán)設(shè)計(jì)模型,隨著產(chǎn)品的發(fā)布,生產(chǎn)周期進(jìn)入了第四階段,然同時(shí)理解階段又重新開(kāi)始了。對(duì)于數(shù)字產(chǎn)品來(lái)說(shuō),這是自然發(fā)生的事前:你發(fā)布一個(gè)產(chǎn)品,基于該版本收集反饋,然后構(gòu)思它,周而復(fù)始,這個(gè)循環(huán)再次產(chǎn)生。
但是,觀(guān)察這個(gè)循環(huán)并建立這些連接僅僅是冰山一角。在數(shù)字時(shí)代發(fā)展起來(lái)的設(shè)計(jì)思維給世界帶來(lái)了許多反思。
變革中的大佬
幸運(yùn)的是,已經(jīng)有許多大品牌意識(shí)到轉(zhuǎn)變的必要性,并采取和提出了數(shù)字設(shè)計(jì)思維方法來(lái)支持轉(zhuǎn)變,并建立循環(huán)設(shè)計(jì)的時(shí)代。根據(jù)《循環(huán)設(shè)計(jì)指南》,“我們應(yīng)該把我們?cè)O(shè)計(jì)的所有東西都看作軟件產(chǎn)品和服務(wù)——這些產(chǎn)品和服務(wù)可以基于我們通過(guò)反饋得到的數(shù)據(jù)而不斷的發(fā)?!?
用戶(hù)體驗(yàn)研究和用戶(hù)體驗(yàn)設(shè)計(jì)一直是在做的一件事是:基于全面的研究和真實(shí)的用戶(hù)需求來(lái)構(gòu)建產(chǎn)品。上面的設(shè)計(jì)指南是非常復(fù)雜的工具,具有許多可能的方法。它強(qiáng)調(diào)了從產(chǎn)品到服務(wù)流程轉(zhuǎn)變的重要性,并展示如何使用敏捷流程并將其實(shí)施到構(gòu)建產(chǎn)品的方法之中。
IDEO(全球頂尖的設(shè)計(jì)咨詢(xún)公司)與 Ellen Macarthur Foundation(艾倫·麥克阿瑟基金會(huì))合作,試圖“試圖通過(guò)設(shè)計(jì)構(gòu)建一個(gè)具有恢復(fù)性和再生性的經(jīng)濟(jì)框架”。在這里,您可以找到幾乎每個(gè)生產(chǎn)方面和領(lǐng)域——例如食品、時(shí)裝、經(jīng)濟(jì)和設(shè)計(jì)——并在每個(gè)領(lǐng)域中提出解決方案,以打破線(xiàn)性生產(chǎn)系統(tǒng)。
耐克還宣布了其基于循環(huán)設(shè)計(jì)模型生產(chǎn)高品質(zhì)運(yùn)動(dòng)鞋的新方法原則。正如您已經(jīng)看到的那樣,無(wú)論您身處哪個(gè)經(jīng)濟(jì)領(lǐng)域,都可以為循環(huán)生產(chǎn)過(guò)程的蓬勃發(fā)展做貢獻(xiàn),并成為一支主導(dǎo)力量。
重要的結(jié)論
我認(rèn)為,作為設(shè)計(jì)師,我們始終要為變革而努力,并始終努力與客戶(hù)、產(chǎn)品或服務(wù)保持緊密的關(guān)系,并通過(guò)構(gòu)思使其不斷完善,以實(shí)現(xiàn)這一目標(biāo)。這是因?yàn)閭ゴ蟮氖虑橹挥型ㄟ^(guò)時(shí)間和不斷的反思才能實(shí)現(xiàn)。在離線(xiàn)世界中,數(shù)字設(shè)計(jì)過(guò)程也有很多東西可以貢獻(xiàn)。希望通過(guò)教育,能有更多的大公司意識(shí)到用戶(hù)真正想要的產(chǎn)品是具有更多功能并可持續(xù)使用的,而不僅僅是將它們當(dāng)作一次性產(chǎn)品,一旦它們不像最初那樣光鮮就把她扔掉。
轉(zhuǎn)自:站酷-大猴兒er
網(wǎng)上可以找到前端開(kāi)發(fā)社區(qū)貢獻(xiàn)的大量工具,這篇文章列出了我最喜歡的一些工具,這些工具給我的工作帶來(lái)了許多便利。
1. EnjoyCSS
老實(shí)說(shuō),雖然我做過(guò)許多前端開(kāi)發(fā),但我并不擅長(zhǎng) CSS。當(dāng)我陷入困境時(shí),EnjoyCSS 是我的大救星。EnjoyCSS 提供了一個(gè)簡(jiǎn)單的交互界面,幫助我設(shè)計(jì)元素,然后自動(dòng)輸出相應(yīng)的 CSS 代碼。
EnjoyCSS 可以輸出 CSS、LESS、SCSS 代碼,并支持指定需要支持哪些瀏覽器及其版本。開(kāi)發(fā)簡(jiǎn)單頁(yè)面時(shí)用起來(lái)比較方便,但不太適合復(fù)雜一點(diǎn)的前端項(xiàng)目(這類(lèi)項(xiàng)目往往需要引入 CSS 框架)。
2. Prettier Playground
Prettier 是一個(gè)代碼格式化工具,支持格式化 JavaScript 代碼(包括 ES2017、JSX、Angular、Vue、Flow、TypeScript 等)。Prettier 會(huì)移除代碼原本的樣式,替換為遵循最佳實(shí)踐的標(biāo)準(zhǔn)化、一致的樣式。IDE 大多支持 Prettier 工具,不過(guò) Prettier 也有在線(xiàn)版本,讓你可以在瀏覽器里格式化代碼。
如果工作電腦不在手邊,使用移動(dòng)端設(shè)備或者臨時(shí)借用別人的電腦查看代碼時(shí),Prettier Playground 非常好用。相比在 IDE 或編輯器下使用 Prettier,個(gè)人更推薦通過(guò) git pre-commit hook 配置 Prettier:hook 可以保證整個(gè)團(tuán)隊(duì)使用統(tǒng)一的配置,免去各自分別配置 IDE 或編輯器的麻煩。如果是老項(xiàng)目,hook 還可以設(shè)置只格式化有改動(dòng)的單個(gè)文件甚至有改動(dòng)的代碼段,避免在 IDE 或編輯器下使用 Prettier 時(shí)不小心格式了大量代碼,淹沒(méi)了 commit 的主要改動(dòng),讓 review 代碼變得十分痛苦。
3. Postman
Postman 一直在我的開(kāi)發(fā)工具箱里,測(cè)試后端 API 接口時(shí)非常好用。GET、POST、DELETE、OPTIONS、PUT 這些方法都支持。毫無(wú)疑問(wèn),你應(yīng)該使用這個(gè)工具。
Postman 之外,Insomnia 也是很流行的 REST API 測(cè)試工具,亮點(diǎn)是支持 GraphQL。不過(guò) Postman 從 去年夏天發(fā)布的 v7.2 起也支持了 GraphQL。
4. StackBlitz
Chidume Nnamdi 盛贊這是每個(gè)用戶(hù)最喜歡的在線(xiàn) IDE。StackBlitz 將大家最喜歡、最常用的 IDE Visual Studio Code 搬進(jìn)了瀏覽器。
StackBlitz 支持一鍵配置 Angular、React、Ionic、TypeScript、RxJS、Svelte 等 JavaScript 框架,也就是說(shuō),只需幾秒你就可以開(kāi)始寫(xiě)代碼了。
我覺(jué)得這個(gè)在線(xiàn) IDE 很有用,特別是可以在線(xiàn)嘗試一些樣例代碼或者庫(kù),否則僅僅嘗試一些新特性就需要花很多時(shí)間在新項(xiàng)目初始化配置上。有了 StackBlitz,無(wú)需在本地從頭搭建環(huán)境,花上幾分鐘就可以試用一個(gè) NPM 包。很棒,不是嗎?
微軟官方其實(shí)也提供了在線(xiàn)版本的 VSCode,可以在瀏覽器內(nèi)使用 VSCode,并且支持開(kāi)發(fā) Node.js 項(xiàng)目(基于 Azure)。不過(guò) StackBlitz 更專(zhuān)注于優(yōu)化前端開(kāi)發(fā)體驗(yàn),界面更加直觀(guān)一點(diǎn),也推出了 beta 版本的 Node.js 支持(基于 GCP,需要填表申請(qǐng))。
5. Bit.dev
軟件開(kāi)發(fā)的基本原則之一就是代碼復(fù)用。代碼復(fù)用減少了開(kāi)發(fā)量,讓你不用從頭開(kāi)發(fā)組件。
這正是 Bit.dev 做的事,分享可重用的組件和片段,降低開(kāi)發(fā)量,加速開(kāi)發(fā)進(jìn)程。
除了公開(kāi)分享,它還支持在團(tuán)隊(duì)分享,讓團(tuán)隊(duì)協(xié)作更方便。
正如 Bit.dev 的口號(hào)「組件即設(shè)計(jì)體系。協(xié)同開(kāi)發(fā)更好的組件。」所言,Bit.dev 可以用來(lái)創(chuàng)建設(shè)計(jì)體系,允許團(tuán)隊(duì)內(nèi)的開(kāi)發(fā)者和設(shè)計(jì)師一起協(xié)作,從頭搭建一套設(shè)計(jì)體系。
Bit.dev 目前支持 React、Vue、Angular、Node 及其他 JavaScript 框架。
在 Bit.dev 上不僅可以搜索組件,還可以直接查看組件的依賴(lài),瀏覽組件的代碼,甚至在線(xiàn)編輯代碼并查看預(yù)覽效果!選好組件后可以通過(guò) Bit.dev 的命令行工具 bit 在本地項(xiàng)目引入組件,也可以通過(guò) npm、yarn 引入組件。
6. CanIUse
CanIUse是非常好用的在線(xiàn)工具,可以方便地查看各大瀏覽器對(duì)某個(gè)特性的支持程度。
我過(guò)去經(jīng)常碰到自己開(kāi)發(fā)的應(yīng)用的一些功能在其他瀏覽器下不支持的情況。比如我的作品集項(xiàng)目使用的某個(gè)特性在 Safari 下不支持,直到項(xiàng)目上線(xiàn)幾個(gè)月后我才意識(shí)到。這些經(jīng)驗(yàn)教訓(xùn)讓我意識(shí)到需要檢查瀏覽器兼容性。
我們來(lái)看一個(gè)例子吧。哪些瀏覽器支持 WebP 圖像格式?
如你所見(jiàn),Safari 和 IE 目前不支持 WebP。這意味著需要為不兼容的瀏覽器提供回退選項(xiàng),比如:
<picture>
CanIUse 還可以在命令行下使用,例如,在命令行下查看 WebP 圖像格式的瀏覽器兼容性:caniuse webp(運(yùn)行命令前需要事先通過(guò) npm install -g caniuse-cmd安裝命令行工具。
方法參數(shù)的驗(yàn)證
JavaScript 允許你設(shè)置參數(shù)的默認(rèn)值。通過(guò)這種方法,可以通過(guò)一個(gè)巧妙的技巧來(lái)驗(yàn)證你的方法參數(shù)。
const isRequired = () => { throw new Error('param is required'); };
const print = (num = isRequired()) => { console.log(`printing ${num}`) };
print(2);//printing 2
print()// error
print(null)//printing null
非常整潔,不是嗎?
格式化 json 代碼
你可能對(duì) JSON.stringify 非常熟悉。但是你是否知道可以用 stringify 進(jìn)行格式化輸出?實(shí)際上這很簡(jiǎn)單。
stringify 方法需要三個(gè)輸入。 value,replacer 和 space。后兩個(gè)是可選參數(shù)。這就是為什么我們以前沒(méi)有注意過(guò)它們。要對(duì) json 進(jìn)行縮進(jìn),必須使用 space 參數(shù)。
console.log(JSON.stringify({name:"John",Age:23},null,'\t'));
>>>
{
"name": "John",
"Age": 23
}
從數(shù)組中獲取唯一值
要從數(shù)組中獲取唯一值,我們需要使用 filter 方法來(lái)過(guò)濾出重復(fù)值。但是有了新的 Set 對(duì)象,事情就變得非常順利和容易了。
let uniqueArray = [...new Set([1, 2, 3, 3, 3, "school", "school", 'ball', false, false, true, true])];
>>> [1, 2, 3, "school", "ball", false, true]
從數(shù)組中刪除虛值(Falsy Value)
在某些情況下,你可能想從數(shù)組中刪除虛值。虛值是 JavaScript 的 Boolean 上下文中被認(rèn)定為為 false 的值。 JavaScript 中只有六個(gè)虛值,它們是:
undefined
null
NaN
0
"" (空字符串)
false
濾除這些虛值的最簡(jiǎn)單方法是使用以下函數(shù)。
myArray.filter(Boolean);
如果要對(duì)數(shù)組進(jìn)行一些修改,然后過(guò)濾新數(shù)組,可以嘗試這樣的操作。請(qǐng)記住,原始的 myArray 會(huì)保持不變。
myArray
.map(item => {
// Do your changes and return the new item
})
.filter(Boolean);
合并多個(gè)對(duì)象
假設(shè)我有幾個(gè)需要合并的對(duì)象,那么這是我的首選方法。
const user = {
name: 'John Ludwig',
gender: 'Male'
};
const college = {
primary: 'Mani Primary School',
secondary: 'Lass Secondary School'
};
const skills = {
programming: 'Extreme',
swimming: 'Average',
sleeping: 'Pro'
};
const summary = {...user, ...college, ...skills};
這三個(gè)點(diǎn)在 JavaScript 中也稱(chēng)為展開(kāi)運(yùn)算符。你可以在這里學(xué)習(xí)更多用法。
對(duì)數(shù)字?jǐn)?shù)組進(jìn)行排序
JavaScript 數(shù)組有內(nèi)置的 sort 方法。默認(rèn)情況下 sort 方法把數(shù)組元素轉(zhuǎn)換為字符串,并對(duì)其進(jìn)行字典排序。在對(duì)數(shù)字?jǐn)?shù)組進(jìn)行排序時(shí),這有可能會(huì)導(dǎo)致一些問(wèn)題。所以下面是解決這類(lèi)問(wèn)題的簡(jiǎn)單解決方案。
[0,10,4,9,123,54,1].sort((a,b) => a-b);
>>> [0, 1, 4, 9, 10, 54, 123]
這里提供了一個(gè)將數(shù)字?jǐn)?shù)組中的兩個(gè)元素與 sort 方法進(jìn)行比較的函數(shù)。這個(gè)函數(shù)可幫助我們接收正確的輸出。
Disable Right Click
禁用右鍵
你可能想要阻止用戶(hù)在你的網(wǎng)頁(yè)上單擊鼠標(biāo)右鍵。
<body oncontextmenu="return false">
<div></div>
</body>
這段簡(jiǎn)單的代碼將為你的用戶(hù)禁用右鍵單擊。
使用別名進(jìn)行解構(gòu)
解構(gòu)賦值語(yǔ)法是一種 JavaScript 表達(dá)式,可以將數(shù)組中的值或?qū)ο蟮闹祷驅(qū)傩苑峙浣o變量。解構(gòu)賦值能讓我們用更簡(jiǎn)短的語(yǔ)法進(jìn)行多個(gè)變量的賦值。
const object = { number: 10 };
// Grabbing number
const { number } = object;
// Grabbing number and renaming it as otherNumber
const { number: otherNumber } = object;
console.log(otherNumber); //10
獲取數(shù)組中的最后一項(xiàng)
可以通過(guò)對(duì) splice 方法的參數(shù)傳入負(fù)整數(shù),來(lái)數(shù)獲取組末尾的元素。
let array = [0, 1, 2, 3, 4, 5, 6, 7]
console.log(array.slice(-1));
>>>[7]
console.log(array.slice(-2));
>>>[6, 7]
console.log(array.slice(-3));
>>>[5, 6, 7]
等待 Promise 完成
在某些情況下,你可能會(huì)需要等待多個(gè) promise 結(jié)束??梢杂?Promise.all 來(lái)并行運(yùn)行我們的 promise。
const PromiseArray = [
Promise.resolve(100),
Promise.reject(null),
Promise.resolve("Data release"),
Promise.reject(new Error('Something went wrong'))];
Promise.all(PromiseArray)
.then(data => console.log('all resolved! here are the resolve values:', data))
.catch(err => console.log('got rejected! reason:', err))
關(guān)于 Promise.all 的主要注意事項(xiàng)是,當(dāng)一個(gè) Promise 拒絕時(shí),該方法將引發(fā)錯(cuò)誤。這意味著你的代碼不會(huì)等到你所有的 promise 都完成。
如果你想等到所有 promise 都完成后,無(wú)論它們被拒絕還是被解決,都可以使用 Promise.allSettled。此方法在 ES2020 的最終版本得到支持。
const PromiseArray = [
Promise.resolve(100),
Promise.reject(null),
Promise.resolve("Data release"),
Promise.reject(new Error('Something went wrong'))];
Promise.allSettled(PromiseArray).then(res =>{
console.log(res);
}).catch(err => console.log(err));
//[
//{status: "fulfilled", value: 100},
//{status: "rejected", reason: null},
//{status: "fulfilled", value: "Data release"},
//{status: "rejected", reason: Error: Something went wrong ...}
//]
即使某些 promise 被拒絕,Promise.allSettled 也會(huì)從你所有的 promise 中返回結(jié)果。
XML(Extensible Markup Language 可擴(kuò)展標(biāo)記語(yǔ)言),XML是一個(gè)以文本來(lái)描述數(shù)據(jù)的文檔。
<?xml version="1.0" encoding="UTF-8"?> <people> <person personid="E01"> <name>Tony</name> <address>10 Downing Street, London, UK</address> <tel>(061) 98765</tel> <fax>(061) 98765</fax> <email>tony@everywhere.com</email> </person> <person personid="E02"> <name>Bill</name> <address>White House, USA</address> <tel>(001) 6400 98765</tel> <fax>(001) 6400 98765</fax> <email>bill@everywhere.com</email> </person> </people>
(1)充當(dāng)顯示數(shù)據(jù)(以XML充當(dāng)顯示層)
(2)存儲(chǔ)數(shù)據(jù)(存儲(chǔ)層)的功能
(3)以XML描述數(shù)據(jù),并在聯(lián)系服務(wù)器與系統(tǒng)的其余部分之間傳遞。(傳輸數(shù)據(jù)的一樣格式)
從某種角度講,XML是數(shù)據(jù)封裝和消息傳遞技術(shù)。
3.解析XML:// 創(chuàng)建SAX解析器工廠(chǎng)對(duì)象 SAXParserFactory spf = SAXParserFactory.newInstance(); // 使用解析器工廠(chǎng)創(chuàng)建解析器實(shí)例 SAXParser saxParser = spf.newSAXParser(); // 創(chuàng)建SAX解析器要使用的事件偵聽(tīng)器對(duì)象 PersonHandler handler = new PersonHandler(); // 開(kāi)始解析文件 saxParser.parse( new File(fileName), handler);
3.2. DOM解析XML:
DOM:Document Object Model(文檔對(duì)象模型)
DOM的特性:
定義一組 Java 接口,基于對(duì)象,與語(yǔ)言和平臺(tái)無(wú)關(guān)將 XML 文檔表示為樹(shù),在內(nèi)存中解析和存儲(chǔ) XML 文檔,允許隨機(jī)訪(fǎng)問(wèn)文檔的不同部分。
DOM解析XML
DOM的優(yōu)點(diǎn),由于樹(shù)在內(nèi)存中是持久的,因此可以修改后更新。它還可以在任何時(shí)候在樹(shù)中上下導(dǎo)航,API使用起來(lái)也較簡(jiǎn)單。
DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance(); DocumentBuilder db = builder.newDocumentBuilder(); db.parse("person.xml"); NodeList node_person = doc.getElementsByTagName("person");
3.3. JDOM解析XML:
JDOM是兩位著名的 Java 開(kāi)發(fā)人員兼作者,Brett Mclaughlin 和 Jason Hunter 的創(chuàng)作成果, 2000 年初在類(lèi)似于A(yíng)pache協(xié)議的許可下,JDOM作為一個(gè)開(kāi)放源代碼項(xiàng)目正式開(kāi)始研發(fā)了。
JDOM 簡(jiǎn)化了與 XML 的交互并且比使用 DOM 實(shí)現(xiàn)更快,JDOM 與 DOM 主要有兩方面不同。首先,JDOM 僅使用具體類(lèi)而不使用接口。這在某些方面簡(jiǎn)化了 API,但是也限制了靈活性。第二,API 大量使用了 Collections 類(lèi),簡(jiǎn)化了那些已經(jīng)熟悉這些類(lèi)的 Java 開(kāi)發(fā)者的使用。
解析步驟: (1)SAXBuilder sax = new SAXBuilder(); (2)Document doc = sax.build(….); (3)Element el = doc.getRootElement();(4)List list = el.getChildren(); (5)遍歷內(nèi)容
解析步驟: (1)SAXReader sax = new SAXReader(); (2)Document doc = sax.read(Thread.currentThread().getContextClassLoader() .getResourceAsStream("person.xml")); (3)Element root = doc.getRootElement(); (4)Iterator iterator = root.elementIterator(); (5)遍歷迭代器
public class Person { private String personid; private String name; private String address; private String tel; private String fax; private String email; @Override public String toString() { return "Person{" + "personid='" + personid + '\'' + ", name='" + name + '\'' + ", address='" + address + '\'' + ", tel='" + tel + '\'' + ", fax='" + fax + '\'' + ", email='" + email + '\'' + '}'; } public String getPersonid() { return personid; } public void setPersonid(String personid) { this.personid = personid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getTel() { return tel; } public void setTel(String tel) { this.tel = tel; } public String getFax() { return fax; } public void setFax(String fax) { this.fax = fax; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }
<?xml version="1.0" encoding="UTF-8"?> <people> <person personid="E01"> <name>Tony Blair</name> <address>10 Downing Street, London, UK</address> <tel>(061) 98765</tel> <fax>(061) 98765</fax> <email>blair@everywhere.com</email> </person> <person personid="E02"> <name>Bill Clinton</name> <address>White House, USA</address> <tel>(001) 6400 98765</tel> <fax>(001) 6400 98765</fax> <email>bill@everywhere.com</email> </person> </people>
import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import java.util.ArrayList; import java.util.List; /** * Created by Hu Guanzhong * SAX解析的特點(diǎn): * 1、基于事件驅(qū)動(dòng) * 2、順序讀取,速度快 * 3、不能任意讀取節(jié)點(diǎn)(靈活性差) * 4、解析時(shí)占用的內(nèi)存小 * 5、SAX更適用于在性能要求更高的設(shè)備上使用(Android開(kāi)發(fā)中) * */ public class PersonHandler extends DefaultHandler{ private List<Person> persons = null; private Person p;//當(dāng)前正在解析的person private String tag;//用于記錄當(dāng)前正在解析的標(biāo)簽名 public List<Person> getPersons() { return persons; } //開(kāi)始解析文檔時(shí)調(diào)用 @Override public void startDocument() throws SAXException { super.startDocument(); persons = new ArrayList<>(); System.out.println("開(kāi)始解析文檔..."); } //在XML文檔解析結(jié)束時(shí)調(diào)用 @Override public void endDocument() throws SAXException { super.endDocument(); System.out.println("解析文檔結(jié)束."); } /** * 解析開(kāi)始元素時(shí)調(diào)用 * @param uri 命名空間 * @param localName 不帶前綴的標(biāo)簽名 * @param qName 帶前綴的標(biāo)簽名 * @param attributes 當(dāng)前標(biāo)簽的屬性集合 * @throws SAXException */ @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { super.startElement(uri, localName, qName, attributes); if ("person".equals(qName)){ p = new Person(); String personid = attributes.getValue("personid"); p.setPersonid(personid); } tag = qName; System.out.println("startElement--"+qName); } //解析結(jié)束元素時(shí)調(diào)用 @Override public void endElement(String uri, String localName, String qName) throws SAXException { super.endElement(uri, localName, qName); if ("person".equals(qName)) { persons.add(p); } tag = null; System.out.println("endElement--"+qName); } //解析文本內(nèi)容時(shí)調(diào)用 @Override public void characters(char[] ch, int start, int length) throws SAXException { super.characters(ch, start, length); if (tag != null) { if ("name".equals(tag)) { p.setName(new String(ch,start,length)); }else if("address".equals(tag)){ p.setAddress(new String(ch,start,length)); }else if("tel".equals(tag)){ p.setTel(new String(ch,start,length)); }else if("fax".equals(tag)){ p.setFax(new String(ch,start,length)); }else if("email".equals(tag)){ p.setEmail(new String(ch,start,length)); } System.out.println(ch); } } }
public class XMLDemo { /** * 使用第三方xstream組件實(shí)現(xiàn)XML的解析與生成 */ @Test public void xStream(){ Person p = new Person(); p.setPersonid("1212"); p.setAddress("北京"); p.setEmail("vince@163.com"); p.setFax("6768789798"); p.setTel("13838389438"); p.setName("38"); XStream xStream = new XStream(new Xpp3Driver()); xStream.alias("person",Person.class); xStream.useAttributeFor(Person.class,"personid"); String xml = xStream.toXML(p); System.out.println(xml); //解析XML Person person = (Person)xStream.fromXML(xml); System.out.println(person); } /** * 從XML文件中讀取對(duì)象 */ @Test public void xmlDecoder() throws FileNotFoundException { BufferedInputStream in = new BufferedInputStream(new FileInputStream("test.xml")); XMLDecoder decoder = new XMLDecoder(in); Person p = (Person)decoder.readObject(); System.out.println(p); } /** * 把對(duì)象轉(zhuǎn)成XML文件寫(xiě)入 */ @Test public void xmlEncoder() throws FileNotFoundException { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("test.xml")); XMLEncoder xmlEncoder = new XMLEncoder(bos); Person p = new Person(); p.setPersonid("1212"); p.setAddress("北京"); p.setEmail("vince@163.com"); p.setFax("6768789798"); p.setTel("13838389438"); p.setName("38"); xmlEncoder.writeObject(p); xmlEncoder.close(); } /** * DOM4J解析XML * 基于樹(shù)型結(jié)構(gòu),第三方組件 * 解析速度快,效率更高,使用的JAVA中的迭代器實(shí)現(xiàn)數(shù)據(jù)讀取,在WEB框架中使用較多(Hibernate) * */ @Test public void dom4jParseXML() throws DocumentException { //1 創(chuàng)建DOM4J的解析器對(duì)象 SAXReader reader = new SAXReader(); InputStream is = Thread.currentThread().getContextClassLoader() .getResourceAsStream("com/vince/xml/person.xml"); org.dom4j.Document doc = reader.read(is); org.dom4j.Element rootElement = doc.getRootElement(); Iterator<org.dom4j.Element> iterator = rootElement.elementIterator(); ArrayList<Person> persons = new ArrayList<>(); Person p = null; while(iterator.hasNext()){ p = new Person(); org.dom4j.Element e = iterator.next(); p.setPersonid(e.attributeValue("personid")); Iterator<org.dom4j.Element> iterator1 = e.elementIterator(); while(iterator1.hasNext()){ org.dom4j.Element next = iterator1.next(); String tag = next.getName(); if("name".equals(tag)){ p.setName(next.getText()); }else if("address".equals(tag)){ p.setAddress(next.getText()); }else if("tel".equals(tag)){ p.setTel(next.getText()); }else if("fax".equals(tag)){ p.setFax(next.getText()); }else if("email".equals(tag)){ p.setEmail(next.getText()); } } persons.add(p); } System.out.println("結(jié)果:"); System.out.println(Arrays.toString(persons.toArray())); } /** * JDOM解析 XML * 1、與DOM類(lèi)似基于樹(shù)型結(jié)構(gòu), * 2、與DOM的區(qū)別: * (1)第三方開(kāi)源的組件 * (2)實(shí)現(xiàn)使用JAVA的Collection接口 * (3)效率比DOM更快 */ @Test public void jdomParseXML() throws JDOMException, IOException { //創(chuàng)建JDOM解析器 SAXBuilder builder = new SAXBuilder(); InputStream is = Thread.currentThread().getContextClassLoader() .getResourceAsStream("com/vince/xml/person.xml"); org.jdom2.Document build = builder.build(is); Element rootElement = build.getRootElement(); List<Person> list = new ArrayList<>(); Person person = null; List<Element> children = rootElement.getChildren(); for(Element element: children){ person = new Person(); String personid = element.getAttributeValue("personid"); person.setPersonid(personid); List<Element> children1 = element.getChildren(); for (Element e: children1){ String tag = e.getName(); if("name".equals(tag)){ person.setName(e.getText()); }else if("address".equals(tag)){ person.setAddress(e.getText()); }else if("tel".equals(tag)){ person.setTel(e.getText()); }else if("fax".equals(tag)){ person.setFax(e.getText()); }else if("email".equals(tag)){ person.setEmail(e.getText()); } } list.add(person); } System.out.println("結(jié)果:"); System.out.println(Arrays.toString(list.toArray())); } /** * DOM解析XML * 1、基于樹(shù)型結(jié)構(gòu),通過(guò)解析器一次性把文檔加載到內(nèi)存中,所以會(huì)比較占用內(nèi)存,可以隨機(jī)訪(fǎng)問(wèn) * 更加靈活,更適合在WEB開(kāi)發(fā)中使用 */ @Test public void domParseXML() throws ParserConfigurationException, IOException, SAXException { //1、創(chuàng)建一個(gè)DOM解析器工廠(chǎng)對(duì)象 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); //2、通過(guò)工廠(chǎng)對(duì)象創(chuàng)建解析器對(duì)象 DocumentBuilder documentBuilder = factory.newDocumentBuilder(); //3、解析文檔 InputStream is = Thread.currentThread().getContextClassLoader() .getResourceAsStream("com/vince/xml/person.xml"); //此代碼完成后,整個(gè)XML文檔已經(jīng)被加載到內(nèi)存中,以樹(shù)狀形式存儲(chǔ) Document doc = documentBuilder.parse(is); //4、從內(nèi)存中讀取數(shù)據(jù) //獲取節(jié)點(diǎn)名稱(chēng)為person的所有節(jié)點(diǎn),返回節(jié)點(diǎn)集合 NodeList personNodeList = doc.getElementsByTagName("person"); ArrayList<Person> persons = new ArrayList<>(); Person p = null; //此循環(huán)會(huì)迭代兩次 for (int i=0;i<personNodeList.getLength();i++){ Node personNode = personNodeList.item(i); p = new Person(); //獲取節(jié)點(diǎn)的屬性值 String personid = personNode.getAttributes().getNamedItem("personid").getNodeValue(); p.setPersonid(personid); //獲取當(dāng)前節(jié)點(diǎn)的所有子節(jié)點(diǎn) NodeList childNodes = personNode.getChildNodes(); for (int j = 0;j<childNodes.getLength();j++){ Node item = childNodes.item(j); String nodeName = item.getNodeName(); if ("name".equals(nodeName)) { p.setName(item.getFirstChild().getNodeValue()); }else if("address".equals(nodeName)){ p.setAddress(item.getFirstChild().getNodeValue()); }else if("tel".equals(nodeName)){ p.setTel(item.getFirstChild().getNodeValue()); }else if("fax".equals(nodeName)){ p.setFax(item.getFirstChild().getNodeValue()); }else if("email".equals(nodeName)){ p.setEmail(item.getFirstChild().getNodeValue()); } } persons.add(p); } System.out.println("結(jié)果:"); System.out.println(Arrays.toString(persons.toArray())); } /** * SAX解析的特點(diǎn): * 1、基于事件驅(qū)動(dòng) * 2、順序讀取,速度快 * 3、不能任意讀取節(jié)點(diǎn)(靈活性差) * 4、解析時(shí)占用的內(nèi)存小 * 5、SAX更適用于在性能要求更高的設(shè)備上使用(Android開(kāi)發(fā)中) * @throws ParserConfigurationException * @throws SAXException * @throws IOException */ @Test public void saxParseXML() throws ParserConfigurationException, SAXException, IOException { //1、創(chuàng)建一個(gè)SAX解析器工廠(chǎng)對(duì)象 SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); //2、通過(guò)工廠(chǎng)對(duì)象創(chuàng)建SAX解析器 SAXParser saxParser = saxParserFactory.newSAXParser(); //3、創(chuàng)建一個(gè)數(shù)據(jù)處理器(需要我們自己來(lái)編寫(xiě)) PersonHandler personHandler = new PersonHandler(); //4、開(kāi)始解析 InputStream is = Thread.currentThread().getContextClassLoader() .getResourceAsStream("com/vince/xml/person.xml"); saxParser.parse(is,personHandler); List<Person> persons = personHandler.getPersons(); for (Person p:persons){ System.out.println(p); } } }
1. 加載和執(zhí)行
盡量將所有的<script>標(biāo)簽放在</body>標(biāo)簽之前,確保腳本執(zhí)行前頁(yè)面已經(jīng)完成了渲染,避免腳本的下載阻塞其他資源(例如圖片)的下載。
合并腳本,減少頁(yè)面中的<script>標(biāo)簽
使用<script>標(biāo)簽的defer和async屬性(兩者的區(qū)別見(jiàn)這里)
通過(guò)Javascript動(dòng)態(tài)創(chuàng)建<script>標(biāo)簽插入文檔來(lái)下載,其不會(huì)影響頁(yè)面其他進(jìn)程
2.數(shù)據(jù)存取
由于作用域鏈的機(jī)制,訪(fǎng)問(wèn)局部變量比訪(fǎng)問(wèn)跨作用域變量更快,因此在函數(shù)中若要多次訪(fǎng)問(wèn)跨作用域變量,則可以用局部變量保存。
避免使用with語(yǔ)句,其會(huì)延長(zhǎng)作用域鏈
嵌套的對(duì)象成員會(huì)導(dǎo)致引擎搜索所有對(duì)象成員,避免使用嵌套,例如window.location.href
對(duì)象的屬性和方法在原型鏈的位置越深,訪(fǎng)問(wèn)的速度也越慢
3.Dom編程
進(jìn)行大段HTML更新時(shí),推薦使用innerHTML,而不是DOM方法
HTML集合是一個(gè)與文檔中元素綁定的類(lèi)數(shù)組對(duì)象,其長(zhǎng)度隨著文檔中元素的增減而動(dòng)態(tài)變化,因此避免在每次循環(huán)中直接讀取HTML集合的length,容易導(dǎo)致死循環(huán)
使用節(jié)點(diǎn)的children屬性,而不是childNodes屬性,前者訪(fǎng)問(wèn)速度更快,且不包含空白文本和注釋節(jié)點(diǎn)。
瀏覽器的渲染過(guò)程包括構(gòu)建DOM樹(shù)和渲染樹(shù),當(dāng)DOM元素的幾何屬性變化時(shí),需要重新構(gòu)造渲染樹(shù),這一過(guò)程稱(chēng)為“重排”,完成重排后,瀏覽器會(huì)重新繪制受影響的部分到屏幕中,這一過(guò)程稱(chēng)為“重繪”。因此應(yīng)該盡量合并多次對(duì)DOM的修改,或者先將元素脫離文檔流(display:none、文檔片段),應(yīng)用修改后,再插入文檔中。
每次瀏覽器的重排時(shí)都會(huì)產(chǎn)生消耗,大多數(shù)瀏覽器會(huì)通過(guò)隊(duì)列化修改并批量執(zhí)行來(lái)優(yōu)化重排過(guò)程,可當(dāng)訪(fǎng)問(wèn)元素offsetTop、scrollTop、clientTop、getComputedStyle等一系列布局屬性時(shí),會(huì)強(qiáng)制瀏覽器立即進(jìn)行重排返回正確的值。因此不要在dom布局信息改變時(shí),訪(fǎng)問(wèn)這些布局屬性。
當(dāng)修改同個(gè)元素多個(gè)Css屬性時(shí),可以使用CssText屬性進(jìn)行一次性修改樣式,減少瀏覽器重排和重繪的次數(shù)
當(dāng)元素發(fā)生動(dòng)畫(huà)時(shí),可以使用絕對(duì)定位使其脫離文檔流,動(dòng)畫(huà)結(jié)束后,再恢復(fù)定位。避免動(dòng)畫(huà)過(guò)程中瀏覽器反復(fù)重排文檔流中的元素。
多使用事件委托,減少監(jiān)聽(tīng)事件
4.算法和流程控制
for循環(huán)和while循環(huán)性能差不多,除了for-in循環(huán)最慢(其要遍歷原型鏈)
循環(huán)中要減少對(duì)象成員及數(shù)組項(xiàng)的查詢(xún)次數(shù),可以通過(guò)倒序循環(huán)提高性能
循環(huán)次數(shù)大于1000時(shí),可運(yùn)用Duff Devices減少迭代次數(shù)
switch比if-else快,但如果具有很多離散值時(shí),可使用數(shù)組或?qū)ο髞?lái)構(gòu)建查找表
遞歸可能會(huì)造成調(diào)用棧溢出,可將其改為循環(huán)迭代
如果可以,對(duì)一些函數(shù)的計(jì)算結(jié)果進(jìn)行緩存
5.字符串和正則表達(dá)式
進(jìn)行大量字符串的連接時(shí),+和+=效率比數(shù)組的join方法要高
當(dāng)創(chuàng)建了一個(gè)正則表達(dá)式對(duì)象時(shí),瀏覽器會(huì)驗(yàn)證你的表達(dá)式,然后將其轉(zhuǎn)化為一個(gè)原生代碼程序,用戶(hù)執(zhí)行匹配工作。當(dāng)你將其賦值給變量時(shí),可以避免重復(fù)執(zhí)行該步驟。
當(dāng)正則進(jìn)入使用狀態(tài)時(shí),首先要確定目標(biāo)字符串的起始搜索位置(字符串的起始位置或正則表達(dá)式的lastIndex屬性),之后正則表達(dá)式會(huì)逐個(gè)檢查文本和正則模式,當(dāng)一個(gè)特定的字元匹配失敗時(shí),正則表達(dá)式會(huì)試著回溯到之前嘗試匹配的位置,然后嘗試其他路徑。如果正則表達(dá)式所有的可能路徑都沒(méi)有匹配到,其會(huì)將起始搜索位置下移一位,重新開(kāi)始檢查。如果字符串的每個(gè)字符都經(jīng)歷過(guò)檢查,沒(méi)有匹配成功,則宣布徹底失敗。
當(dāng)正則表達(dá)式不那么具體時(shí),例如.和[\s\S]等,很可能會(huì)出現(xiàn)回溯失控的情況,在js中可以應(yīng)用預(yù)查模擬原子組(?=(pattern))\1來(lái)避免不必要的回溯。除此之外,嵌套的量詞,例如/(A+A+)+B/在匹配"AAAAAAAA"時(shí)可能會(huì)造成驚人的回溯,應(yīng)盡量避免使用嵌套的量詞或使用預(yù)查模擬原子組消除回溯問(wèn)題。
將復(fù)雜的正則表達(dá)式拆分為多個(gè)簡(jiǎn)單的片段、正則以簡(jiǎn)單、必需的字元開(kāi)始、減少分支數(shù)量|,有助于提高匹配的效率。
setTimeout(function(){ process(todo.shift()); if (todo.length > 0) { setTimeout(arguments.callee, 25); } else { callback(); } })
setTimeout(function(){ let start = +new Date(); do { process(todo.shift()); } while(todo.length > 0 && (+new Date() - start) < 50) if (todo.length > 0) { setTimeout(arguments.callee, 25); } else { callback(); } })
WebWork
進(jìn)行計(jì)算
Expires: Mon,28 Jul 2018 23:30:30 GMT
eval
、Function
進(jìn)行雙重求值
Object
/Array
字面量定義,不要使用構(gòu)造函數(shù)
if (i & 1) { className = 'odd'; } else { className = 'even'; }
Math
對(duì)象等
背景
這一個(gè)因?yàn)闈L動(dòng)條占據(jù)空間引起的bug, 查了一下資料, 最后也解決了,順便研究一下這個(gè)屬性, 做一下總結(jié),分享給大家看看。
正文
昨天, 測(cè)試提了個(gè)問(wèn)題, 現(xiàn)象是一個(gè)輸入框的聚焦提示偏了, 讓我修一下, 如下圖:
image.png
起初認(rèn)為是紅框提示位置不對(duì), 就去找代碼看:
<Input
// ...
onFocus={() => setFocusedInputName('guidePrice')}
onBlur={() => setFocusedInputName('')}
/>
<Table
data-focused-column={focusedInputName}
// ...
/>
代碼上沒(méi)有什么問(wèn)題, 不是手動(dòng)設(shè)置的,而且, 在我和另一個(gè)同事, 還有PM的PC上都是OK的:
image.png
初步判斷是,紅框位置結(jié)算有差異, 差異大小大概是17px, 但是這個(gè)差異是怎么產(chǎn)生的呢?
就去測(cè)試小哥的PC上看, 注意到一個(gè)細(xì)節(jié), 在我PC上, 滾動(dòng)條是懸浮的:
image.png
在他PC上, 滾動(dòng)條是占空間的:
image.png
在他電腦上, 手動(dòng)把原本的 overscroll-y: scroll 改成 overscroll-y: overlay 問(wèn)題就結(jié)局了。
由此判定是: 滾動(dòng)條占據(jù)空間 引起的bug。
overscroll-y: overlay
CSS屬性 overflow, 定義當(dāng)一個(gè)元素的內(nèi)容太大而無(wú)法適應(yīng)塊級(jí)格式化上下文的時(shí)候該做什么。它是 overflow-x 和overflow-y的 簡(jiǎn)寫(xiě)屬性 。
/* 默認(rèn)值。內(nèi)容不會(huì)被修剪,會(huì)呈現(xiàn)在元素框之外 */
overflow: visible;
/* 內(nèi)容會(huì)被修剪,并且其余內(nèi)容不可見(jiàn) */
overflow: hidden;
/* 內(nèi)容會(huì)被修剪,瀏覽器會(huì)顯示滾動(dòng)條以便查看其余內(nèi)容 */
overflow: scroll;
/* 由瀏覽器定奪,如果內(nèi)容被修剪,就會(huì)顯示滾動(dòng)條 */
overflow: auto;
/* 規(guī)定從父元素繼承overflow屬性的值 */
overflow: inherit;
官方描述:
overlay 行為與 auto 相同,但滾動(dòng)條繪制在內(nèi)容之上而不是占用空間。 僅在基于 WebKit(例如,Safari)和基于Blink的(例如,Chrome或Opera)瀏覽器中受支持。
表現(xiàn):
html {
overflow-y: overlay;
}
兼容性
沒(méi)有在caniuse上找到這個(gè)屬性的兼容性, 也有人提這個(gè)問(wèn)題:
image.png
問(wèn)題場(chǎng)景以及解決辦法
1. 外部容器的滾動(dòng)條
這里的外部容器指的是html, 直接加在最外層:
html {
overflow-y: scroll;
}
手動(dòng)加上這個(gè)特性, 不論什么時(shí)候都有滾動(dòng)寬度占據(jù)空間。
缺點(diǎn): 沒(méi)有滾動(dòng)的時(shí)候也會(huì)有個(gè)滾動(dòng)條, 不太美觀(guān)。
優(yōu)點(diǎn): 方便, 沒(méi)有兼容性的問(wèn)題。
2. 外部容器絕對(duì)定位法
用絕對(duì)定位,保證了body的寬度一直保持完整空間:
html {
overflow-y: scroll; // 兼容ie8,不支持:root, vw
}
:root {
overflow-y: auto;
overflow-x: hidden;
}
:root body {
position: absolute;
}
body {
width: 100vw;
overflow: hidden;
}
3. 內(nèi)部容器做兼容
.wrapper {
overflow-y: scroll; // fallback
overflow-y: overlay;
}
總結(jié)
個(gè)人推薦還是用 overlay, 然后使用scroll 做為兜底。
內(nèi)容就這么多, 希望對(duì)大家有所啟發(fā)。
文章如有錯(cuò)誤, 請(qǐng)?jiān)诹粞詤^(qū)指正, 謝謝。
之前花了些時(shí)間將gatsby-theme-gitbook遷移到 Typescript,以獲得在 VSCode 中更好的編程體驗(yàn).
整體差不多已經(jīng)完成遷移,剩下將 Gatsby 的 API 文件也遷移到 TS,這里可以看到 gatsby#21995 官方也在將核心代碼庫(kù)遷移到 Typescript,準(zhǔn)備等待官方將核心代碼庫(kù)遷移完成,在遷移 API 文件.
這篇文章用XYShaoKang/gatsby-project-config,演示如何將 gatsby 遷移到 TypeScript,希望能幫到同樣想要在 Gatsby 中使用 TS 的同學(xué).
遷移步驟:
TS 配置
配置 ESLint 支持 TS
完善 GraphQL 類(lèi)型提示
初始化項(xiàng)目
gatsby new gatsby-migrate-to-typescript XYShaoKang/gatsby-project-config
cd gatsby-migrate-to-typescript
yarn develop
TS 配置
安裝typescript
添加typescript.json配置文件
修改 js 文件為 tsx
補(bǔ)全 TS 聲明定義
安裝typescript
yarn add -D typescript
添加配置文件tsconfig.json
// https://www.typescriptlang.org/v2/docs/handbook/tsconfig-json.html
{
"compilerOptions": {
"target": "esnext", // 編譯生成的目標(biāo) es 版本,可以根據(jù)需要設(shè)置
"module": "esnext", // 編譯生成的目標(biāo)模塊系統(tǒng)
"lib": ["dom", "es2015", "es2017"], // 配置需要包含的運(yùn)行環(huán)境的類(lèi)型定義
"jsx": "react", // 配置 .tsx 文件的輸出模式
"strict": true, // 開(kāi)啟嚴(yán)格模式
"esModuleInterop": true, // 兼容 CommonJS 和 ES Module
"moduleResolution": "node", // 配置模塊的解析規(guī)則,支持 node 模塊解析規(guī)則
"noUnusedLocals": true, // 報(bào)告未使用的局部變量的錯(cuò)誤
"noUnusedParameters": true, // 報(bào)告有關(guān)函數(shù)中未使用參數(shù)的錯(cuò)誤
"experimentalDecorators": true, // 啟用裝飾器
"emitDecoratorMetadata": true, // 支持裝飾器上生成元數(shù)據(jù),用來(lái)進(jìn)行反射之類(lèi)的操作
"noEmit": true, // 不輸出 js,源映射或聲明之類(lèi)的文件,單純用來(lái)檢查錯(cuò)誤
"skipLibCheck": true // 跳過(guò)聲明文件的類(lèi)型檢查,只會(huì)檢查已引用的部分
},
"exclude": ["./node_modules", "./public", "./.cache"], // 解析時(shí),應(yīng)該跳過(guò)的路晉
"include": ["src"] // 定義包含的路徑,定義在其中的聲明文件都會(huì)被解析進(jìn) vscode 的智能提示
}
將index.js改成index.tsx,重新啟動(dòng)服務(wù),查看效果.
其實(shí) Gatsby 內(nèi)置了支持 TS,不用其他配置,只要把index.js改成index.tsx就可以直接運(yùn)行.添加 TS 依賴(lài)是為了顯示管理 TS,而tsconfig.json也是這個(gè)目的,當(dāng)我們有需要新的特性以及自定義配置時(shí),可以手動(dòng)添加.
補(bǔ)全 TS 聲明定義
打開(kāi)index.tsx,VSCode 會(huì)報(bào)兩個(gè)錯(cuò)誤,一個(gè)是找不到styled-components的聲明文件,這個(gè)可以通過(guò)安裝@types/styled-components來(lái)解決.
另外一個(gè)錯(cuò)誤綁定元素“data”隱式具有“any”類(lèi)型。,這個(gè)錯(cuò)誤是因?yàn)槲覀冊(cè)趖sconfig.json中指定了"strict": true,這會(huì)開(kāi)啟嚴(yán)格的類(lèi)型檢查,可以通過(guò)關(guān)閉這個(gè)選項(xiàng)來(lái)解決,只是我們用 TS 就是要用它的類(lèi)型檢查的,所以正確的做法是給data定義類(lèi)型.
下面來(lái)一一修復(fù)錯(cuò)誤.
安裝styled-components的聲明文件
yarn add -D @types/styled-components
修改index.tsx
import React, { FC } from 'react'
import styled from 'styled-components'
import { graphql } from 'gatsby'
import { HomeQuery } from './__generated__/HomeQuery'
const Title = styled.h1`
font-size: 1.5em;
margin: 0;
padding: 0.5em 0;
color: palevioletred;
background: papayawhip;
`
const Content = styled.div`
margin-top: 0.5em;
`
interface PageQuery {
data: {
allMarkdownRemark: {
edges: Array<{
node: {
frontmatter: {
title: string
}
excerpt: string
}
}>
}
}
}
const Home: FC<PageQuery> = ({ data }) => {
const node = data.allMarkdownRemark.edges[0].node
const title = node.frontmatter?.title
const excerpt = node.excerpt
return (
<>
<Title>{title}</Title>
<Content>{excerpt}</Content>
</>
)
}
export default Home
export const query = graphql`
query HomeQuery {
allMarkdownRemark {
edges {
node {
frontmatter {
title
}
excerpt
}
}
}
}
`
這時(shí)候會(huì)出現(xiàn)一個(gè)新的錯(cuò)誤,在excerpt: string處提示Parsing error: Unexpected token,這是因?yàn)?ESLint 還無(wú)法識(shí)別 TS 的語(yǔ)法,下面來(lái)配置 ESLint 支持 TS.
配置 ESLint 支持 TypeScript
安裝依賴(lài)
yarn add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
配置.eslintrc.js
module.exports = {
parser: `@typescript-eslint/parser`, // 將解析器從`babel-eslint`替換成`@typescript-eslint/parser`,用以解析 TS 代碼
extends: [
`google`,
`eslint:recommended`,
`plugin:@typescript-eslint/recommended`, // 使用 @typescript-eslint/eslint-plugin 推薦配置
`plugin:react/recommended`,
`prettier/@typescript-eslint`, // 禁用 @typescript-eslint/eslint-plugin 中與 prettier 沖突的規(guī)則
`plugin:prettier/recommended`,
],
plugins: [
`@typescript-eslint`, // 處理 TS 語(yǔ)法規(guī)則
`react`,
`filenames`,
],
// ...
}
在.vscode/settings.json中添加配置,讓VSCode使用ESLint擴(kuò)展格式化ts和tsx文件
// .vscode/settings.json
{
"eslint.format.enable": true,
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[javascriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[typescriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
}
}
完善 GraphQL 類(lèi)型提示
// index.tsx
import React, { FC } from 'react'
// ...
interface PageQuery {
data: {
allMarkdownRemark: {
edges: Array<{
node: {
frontmatter: {
title: string
}
excerpt: string
}
}>
}
}
}
const Home: FC<PageQuery> = ({ data }) => {
// ...
}
export default Home
export const query = graphql`
query HomeQuery {
allMarkdownRemark {
edges {
node {
frontmatter {
title
}
excerpt
}
}
}
}
`
我們看看index.tsx文件,會(huì)發(fā)現(xiàn)PropTypes和query結(jié)構(gòu)非常類(lèi)似,在Gatsby運(yùn)行時(shí),會(huì)把query查詢(xún)的結(jié)果作為組件prop.data傳入組件,而PropTypes是用來(lái)約束prop存在的.所以其實(shí)PropTypes就是根據(jù)query寫(xiě)出來(lái)的.
如果有依據(jù)query自動(dòng)生成PropTypes的功能就太棒了.
另外一個(gè)問(wèn)題是在query中編寫(xiě)GraphQL查詢(xún)時(shí),并沒(méi)有類(lèi)型約束,也沒(méi)有智能提示.
總結(jié)以下需要完善的體驗(yàn)包括:
GraphQL 查詢(xún)編寫(xiě)時(shí)的智能提示,以及錯(cuò)誤檢查
能夠從 GraphQL 查詢(xún)生成對(duì)應(yīng)的 TypeScript 類(lèi)型.這樣能保證類(lèi)型的唯一事實(shí)來(lái)源,并消除 TS 中冗余的類(lèi)型聲明.畢竟如果經(jīng)常需要手動(dòng)更新兩處類(lèi)型,會(huì)更容易出錯(cuò),而且也并不能保證手動(dòng)定義類(lèi)型的正確性.
實(shí)現(xiàn)方式:
通過(guò)生成架構(gòu)文件,配合Apollo GraphQL for VS Code插件,實(shí)現(xiàn)智能提示,以及錯(cuò)誤檢查
通過(guò)graphql-code-generator或者apollo生成 TS 類(lèi)型定義文件
如果自己去配置的話(huà),是挺耗費(fèi)時(shí)間的,需要去了解graphql-code-generator的使用,以及Apollo的架構(gòu)等知識(shí).
不過(guò)好在社區(qū)中已經(jīng)有對(duì)應(yīng)的 Gatsby 插件集成了上述工具可以直接使用,能讓我們不用去深究對(duì)應(yīng)知識(shí)的情況下,達(dá)到優(yōu)化 GraphQL 編程的體驗(yàn).
嘗試過(guò)以下兩個(gè)插件能解決上述問(wèn)題,可以任選其一使用
gatsby-plugin-codegen
gatsby-plugin-typegen
另外還有一款插件gatsby-plugin-graphql-codegen也可以生成 TS 類(lèi)型,不過(guò)配置略麻煩,并且上述兩個(gè)插件都可以滿(mǎn)足我現(xiàn)在的需求,所以沒(méi)有去嘗試,感興趣的可以嘗試一下.
注意點(diǎn):
Apollo不支持匿名查詢(xún),需要使用命名查詢(xún)
第一次生成,需要運(yùn)行Gatsby之后才能生成類(lèi)型文件
整個(gè)項(xiàng)目?jī)?nèi)不能有相同命名的查詢(xún),不然會(huì)因?yàn)槊钟袥_突而生成失敗
下面是具體操作
安裝vscode-apollo擴(kuò)展
在 VSCode 中按 Ctrl + P ( MAC 下: Cmd + P) 輸入以下命令,按回車(chē)安裝
ext install apollographql.vscode-apollo
方式一: 使用gatsby-plugin-codegen
gatsby-plugin-codegen默認(rèn)會(huì)生成apollo.config.js和schema.json,配合vscode-apollo擴(kuò)展,可以提供GraphQL的類(lèi)型約束和智能提示.
另外會(huì)自動(dòng)根據(jù)query中的GraphQL查詢(xún),生成 TS 類(lèi)型,放在對(duì)應(yīng)的tsx文件同級(jí)目錄下的__generated__文件夾,使用時(shí)只需要引入即可.
如果需要在運(yùn)行時(shí)自動(dòng)生成 TS 類(lèi)型,需要添加watch: true配置.
安裝gatsby-plugin-codegen
yarn add gatsby-plugin-codegen
配置gatsby-config.js
// gatsby-config.js
module.exports = {
plugins: [
// ...
{
resolve: `gatsby-plugin-codegen`,
options: {
watch: true,
},
},
],
}
重新運(yùn)行開(kāi)發(fā)服務(wù)生成類(lèi)型文件
yarn develop
如果出現(xiàn)以下錯(cuò)誤,一般是因?yàn)闆](méi)有為查詢(xún)命名的緣故,給查詢(xún)添加命名即可,另外配置正確的話(huà),打開(kāi)對(duì)應(yīng)的文件,有匿名查詢(xún),編輯器會(huì)有錯(cuò)誤提示.
fix-anonymous-operations.png
這個(gè)命名之后會(huì)作為生成的類(lèi)型名.
修改index.tsx以使用生成的類(lèi)型
gatsby-plugin-codegen插件會(huì)更具查詢(xún)生成對(duì)應(yīng)的查詢(xún)名稱(chēng)的類(lèi)型,保存在對(duì)應(yīng)tsx文件同級(jí)的__generated__目錄下.
import { HomeQuery } from './__generated__/HomeQuery' // 引入自動(dòng)生成的類(lèi)型
// ...
// interface PageQuery {
// data: {
// allMarkdownRemark: {
// edges: Array<{
// node: {
// frontmatter: {
// title: string
// }
// excerpt: string
// }
// }>
// }
// }
// }
interface PageQuery {
data: HomeQuery // 替換之前手寫(xiě)的類(lèi)型
}
// ...
將自動(dòng)生成的文件添加到.gitignore中
apollo.config.js,schema.json,__generated__能通過(guò)運(yùn)行時(shí)生成,所以可以添加到.gitignore中,不用提交到 git 中.當(dāng)然如果有需要也可以選擇提交到 git 中.
# Generated types by gatsby-plugin-codegen
__generated__
apollo.config.js
schema.json
方式二: 使用gatsby-plugin-typegen
gatsby-plugin-typegen通過(guò)配置生成gatsby-schema.graphql和gatsby-plugin-documents.graphql配合手動(dòng)創(chuàng)建的apollo.config.js提供GraphQL的類(lèi)型約束和智能提示.
根據(jù)GraphQL查詢(xún)生成gatsby-types.d.ts,生成的類(lèi)型放在命名空間GatsbyTypes下,使用時(shí)通過(guò)GatsbyTypes.HomeQueryQuery來(lái)引入,HomeQueryQuery是由對(duì)應(yīng)的命名查詢(xún)生成
安裝gatsby-plugin-typegen
yarn add gatsby-plugin-typegen
配置
// gatsby-config.js
module.exports = {
plugins: [
// ...
{
resolve: `gatsby-plugin-typegen`,
options: {
outputPath: `src/__generated__/gatsby-types.d.ts`,
emitSchema: {
'src/__generated__/gatsby-schema.graphql': true,
},
emitPluginDocuments: {
'src/__generated__/gatsby-plugin-documents.graphql': true,
},
},
},
],
}
//apollo.config.js
module.exports = {
client: {
tagName: `graphql`,
includes: [
`./src/**/*.{ts,tsx}`,
`./src/__generated__/gatsby-plugin-documents.graphql`,
],
service: {
name: `GatsbyJS`,
localSchemaFile: `./src/__generated__/gatsby-schema.graphql`,
},
},
}
重新運(yùn)行開(kāi)發(fā)服務(wù)生成類(lèi)型文件
yarn develop
修改index.tsx以使用生成的類(lèi)型
gatsby-plugin-codegen插件會(huì)更具查詢(xún)生成對(duì)應(yīng)的查詢(xún)名稱(chēng)的類(lèi)型,保存在對(duì)應(yīng)tsx文件同級(jí)的__generated__目錄下.
// ...
// interface PageQuery {
// data: {
// allMarkdownRemark: {
// edges: Array<{
// node: {
// frontmatter: {
// title: string
// }
// excerpt: string
// }
// }>
// }
// }
// }
interface PageQuery {
data: GatsbyTypes.HomeQueryQuery // 替換之前手寫(xiě)的類(lèi)型
}
// ...
將自動(dòng)生成的文件添加到.gitignore中
__generated__能通過(guò)運(yùn)行時(shí)生成,所以可以添加到.gitignore中,不用提交到 git 中.當(dāng)然如果有需要也可以選擇提交到 git 中.
# Generated types by gatsby-plugin-codegen
__generated__
藍(lán)藍(lán)設(shè)計(jì)的小編 http://www.yvirxh.cn