身為用戶體驗設計師,無時無刻不被世界上的新事物沖刷著認知——互聯網紅利下降帶來變化莫測的商業動向、循著摩爾定律野蠻生長日新月異的新技術、各類亞文化群體催生出多元復雜的圈層文化、腦洞口味越來越獨特的年輕人,甚至眼下席卷全球的黑天鵝事件……
任何一個新事物的悄悄冒頭,都有可能在未知的將來影響著用戶體驗設計師。我們能做的是,在起初感受到微微震幅時,便沿著震感逐步尋找源頭,并思考未來的發展走向。趕在變化降臨前先擁抱變化。
本文通過研究近一兩年科技、社會文化以及自身用戶體驗領域的變化,從用戶體驗領域關鍵的用戶、媒介(設備與應用)、交互行為、信息與場景的五個角度出發,探索用戶體驗設計未來的趨勢,希望能帶來啟發。
隨著人工時代到來,過去機械的單向交互方式逐漸被打破,機器漸漸演化成了會主動「觀察」真實場景,「感受」用戶情感,預判用戶意圖并自動完成任務的貼心小棉襖。機器如何為人們提供更智能便捷的服務,未來還有非常大的想象空間。
1. 基于真實場景推理用戶意圖
隨著AI技術的發展,智能設備可以越來越無縫地將數字世界和物理世界嫁接起來,主動感知用戶所處情境并智能提供相應服務。
在2019的 Google I/O 大會上,Google Lens 展示的AR點菜功能可以智能識別用戶掃描的菜單并將美食網站上的相關推薦直接呈現在屏幕上。
當用 Google Lens 識別到小票信息時,可快速提取小票上的金額,且可自動彈起計算器快速幫助用戶計算人均消費,節省人工計算的時間成本。
隨著信息入口從數字空間延伸到周圍的物理空間中,未來萬物皆可為用戶體驗的媒介,設計師未來在設計的時候需要注意:
尋找適合的打通真實世界的切入點:在陌生語言、信息復雜或者難以處理等苛刻的環境下,充分發揮智能設備對信息智能讀取、批量識別與翻譯等強大能力,幫助用戶完成任務;
將用戶旅程的上下游串聯:根據生活常識和經驗預判用戶行為目的,前置推薦服務;
更加系統細心地考量干擾因素:真實場景是動態變化的,需要更全方位考慮光線的強弱、多源的噪音、實體的可視性、人員和事件的打斷等因素。
2. 任務自動化,簡化用戶旅程
為了完成一項任務,用戶往往需要借助多個應用來回切換配合,使用起來瑣碎麻煩。如今應用越做越強大也越復雜,過去僅僅解決單一場景的解決方案不再適應于用戶對于完成任務的訴求。
Google Assistant 的新能力 Duplex on the web 可以通過自動跨應用任務處理來簡化用戶旅程。只需要用戶發出語音指令「預定一輛去某地的車」,助手便可自動跨郵件、日歷、付款等應用調取信息、自動根據使用習慣做選擇,并自動填寫信息,而用戶全程需要的只是在關鍵節點輕敲「確認」即可。
2019年隨著 iOS 13 的更新,「快捷指令」推出了「自動化」能力,用戶通過「if…then…」語法便可為自己的App設計一套程序,實現如:「當我回到公司時提醒我打卡」、「每天早上10點給我的女朋友發送一條表白短信」等能力,將不相關的場景動作串聯字一起自動化執行,大大節省人工操作成本。
提升使用效率是用戶體驗設計孜孜不倦努力的方向之一。在利用新技術進一步簡化用戶旅程時,設計師可以充分利用以下因素:
借助語音輸入:比起界面觸控操作,語音交互的直達性可以「穿透」復雜界面,讓設備第一時間明確用戶目標;
基于用戶行為形成習慣記憶:對用戶長期重復的行為做分析處理,構建用戶習慣模型并主動提供服務;
適當考慮專家級用戶:隨著部分用戶的智能設備使用水平越來越高,可以考慮為專家用戶提供自定義操作腳本,滿足其自身的獨特需求。
3. 基于情感感知,主動理解用戶需求
隨著人臉識別、表情識別、肢體跟蹤等技術的提升,機器逐漸學會感性語言,主動感知用戶內在情感和心理需求。
2019年1月的CES展上起亞亮相的互動式「情感駕駛空間」技術,可通過傳感器讀取用戶的面部表情、心率等反應,調整駕駛空間內的燈光、影片類型、音樂風格等,舒緩艙內乘客心情,由此提供更人性化的出行體驗。
用戶總是會期待更貼心的服務,設計師未來對同理心的情感嗅覺更加敏銳:
利用感性線索定位用戶情緒:需要通過面部表情、特殊時間節點或者識別到的關鍵詞,對用戶情緒進行理解和定位,判斷用戶情感理解用戶內心訴求是自由探索、趣味娛樂、或者靜謐修行并提供符合用戶當下心境的服務。
綜合使用感性元素進行設計:通過使用線條、色彩、聲音和動作等傳達并喚起相對應的情感,提供更加人性化的體驗。
4. 小結
更智能的服務提供方式會讓人們生活擁有更多可能性,但一旦火候把握不得當,可能就會造成對人們生活的野蠻入侵。關于如何讓科技更好造福于人們,早在上個世紀,施樂帕克研究中心提出了寧靜技術(Calm Technology)的愿景,認為影響最深遠的技術應該是隱匿不見的,它們如纖維般融入日常生活,絲絲入扣,直至不可分辨。
隨著科技的發展,設計師對新技術不應是不加克制地應用,而應該潤物細無聲般地提供服務,幫助人們從繁雜喧囂的數字世界中解脫出來,將寶貴的注意力資源投放在讓生活更美好的事物上。
回顧人類和機器的交流語言,從命令行界面、圖形用戶界面到自然用戶界面,人機交互方式越來越貼合人與人之間更自然的交流方式,其背后是心智模型與實現模型的高度擬合的趨勢。
在自然用戶界面中,為滿足新形態智能硬件對新接口的需求,以及人們對更豐富強大的交互方式的自然訴求,越來越多的自然用戶界面被開發出來。語音交互和隔空手勢交互便是近幾年迅速發展并落地的兩種交互方式。
1. 隔空手勢交互:更自由、更靈動
為了讓機器更好地讀懂用戶的身體語言,能夠感知深度信息的攝像頭走進了日常手機。2019年國內外手機廠商的發布大會上,LG 手機 G8 ThinQ 以及華為發布 Mate 30 系列推出的隔空手勢,可實現一些簡單的諸如滑動、切歌、截屏等效果。
除此以外,隔空手勢支持更加細微的手勢,如旋轉、揉搓等,可以更直觀、更靈活的方式操控界面,讓用戶獲得一種像魔術師用意念控制事物運作的快感。
對于隔空手勢操作網上的言論褒貶不一,其中爭議性最大的就是隔空手勢宛如「殺雞用牛刀」,明明可以用更加精準的手勢觸控,為什么還要用看似很酷炫其實精準度更低的隔空手勢操作?
隔空手勢并不是要替代觸控手勢成為主流的人機交互方式,更多是對情境式障礙場景的補充。在某些場景下,用戶使用設備的條件可能是充滿干擾的。想想看當你邊看手機食譜邊炒菜的時候、邊煲劇邊剝小龍蝦的時候、疫情期間出門佩戴橡膠手套無法正常觸控手機屏幕時…..隔空手勢是不是特別好用?
每個人在特殊的場景下都有可能面臨感官障礙,未來的設計也應該更多地考慮情境式障礙的場景,讓用戶無論身處何時何地依舊能一如既往無障礙地使用設備。
2. 語音交互:更精準、更好玩
語音交互作為更趨近于人與人之間最自然的交流方式,近些年有許多發展的突破點。
在發展主線上,語音交互趨向更自然、更人性化、更個性化。過去反人類的一些溝通方式慢慢被「調教」。此外,多人會話場景下的技術方案日漸增多。
2019的 Google I/O 大會展示了一個視頻片段,視頻中的兩位嘉賓相繼吐槽,經常出現針鋒相對難以聽清的時候,這時用戶可以調節音源音量選擇性增強自己關注的人物聲音,讓另一個人「靜音」。
△ 滑動選擇音源
此外,語音交互除了在智能音箱領域廣泛應用以外,也逐漸應用在廣告等更多的傳播媒介中,刷新人們日常使用體驗。2020年2月索尼提交了一項廣告播放新專利。當用戶在觀看電視節目時,如果出現廣告,只要站起來大喊廣告中對應品牌的名字,便可直接跳過這個廣告。
設計師在語音交互場景下,需要留意以下幾個比較容易被忽視的因素:
用戶語音交互習慣培養:如今還處于培養用戶語音交互使用習慣階段,設計師需要更多地考慮應用的語音交互規則如何才能更趨近于人們日常的溝通習慣,并進一步為人們的社會習俗所接納。
真實場景下的多人音源:在現實情境中, 在多人對話場景下將面臨音源不清、穿插停頓、噪音過多等影響體驗的情況,由于計算機聽覺分析能力開始從單人音源拓寬到了多人音源,多人對話解決方案上還有很大想象空間。
改變傳統的視聽體驗:在使用場景上,語音交互接口也將逐漸運用到更多的媒介上,更全面地刷新用戶體驗。
3. 小結
人類擁有雙手、眼睛、耳朵和發聲的嘴巴,但是并不總是在每個使用場景下都能自如地使用:在安靜的自習室下聲音收到限制,在駕駛場景下注意力受到限制,在雙手拎著東西場景下雙手受到限制……但目前許多產品設計都建立在用戶能完整使用感官功能這一理想化的基礎上。
未來的發展趨勢傾向于將視、聽、觸、嗅等多通道信息完美整合起來,綜合使用多種輸入通道和輸出通道,根據用戶使用場景用最恰當的方式傳遞服務,滿足用戶多方位的需求。
盡管喬布斯曾斷言3.5英寸是手機的黃金尺寸,但作為人們日常內容消費與娛樂的窗口,手機屏幕毫無疑問地變得越來越大,甚至超出傳統物理限制。人們對大屏享受的追求與設備攜帶便捷性之間的矛盾由來已久,硬件形態的變化對舊有的用戶體驗設計思路帶來的新的挑戰。
屏幕橫縱比越來越大,而人類的手部具有先天限制,曾經慣用的界面布局方式在高橫縱比的屏幕上可能無法被大拇指無障礙全覆蓋,使得越來越多的設計更加重視利用移動屏幕下半部分。
操作與信息進一步下移:
高德地圖、蘋果地圖的搜索框下移,方便單手操作用戶快速激活輸入框;
影視資訊平臺IMDB強化底部標簽欄功能,雙擊「搜索」tab即可激活輸入框,無須艱難地觸摸頂部。
即時戰斗類手游皇室戰爭的說明卡片主要展示在下半部分,方便用戶進行卡片上的相關操作。
底部導航被賦予更多能力:
Pocket的底部標簽欄現在兼任漢堡菜單功能,在激活狀態下再次點擊主頁icon可選擇主頁上須展示的內容。
利用下滑手勢代替點擊:
Snapchat的許多表示前后進退關系的頁面都不是」返回「按鈕,而是向下箭頭,用戶可下滑退出當前頁面。
為了解決設備形態和人類手部先天限制之間的矛盾,折疊屏誕生瀏覽并顛覆舊有的界面設計方式。
更靈活的信息布局
過去在單屏設計下,考慮到用戶注意力由上到下縱向衰減,因此信息布局更多是按照優先級從上往下排序。而折疊屏中,屏幕展開后便可以開辟出更大的可利用空間,將次級頁面或者較為重要的內容曝光在第二屏,對信息的布局將帶來全新的變化。設計師為保證大小屏下順暢的閱讀體驗,需要對信息模塊在不同空間布局下的流動性有更強的把控能力。
更便捷的多任務操作
在過去的單屏體驗中,用戶只能將注意力完全集中在當前的界面中,一次只做一件事。但在實際生活中,用戶面臨的情景往往是主線任務和支線任務的頻繁交錯,并且根據會任務不同的性質自由調動自己的注意力重心,如邊看視頻邊聊天、邊看直播邊逛街等等。在折疊屏中,設計師可以探索更多主線和支線交錯進行的場景,利用折疊屏帶來的更大的屏幕空間,可以讓用戶在不離開主線場景的基礎上進行支線任務的處理,大大節約了在不同App上來回切換的操作成本。
更直觀的拖拽交互
此外,隨著多任務處理越來越廣泛使用,拖拽交互將成為重要的交互模式之一。文本、表情包、圖片、視頻等交互對象,不再需要經過復雜的分享轉發流程才能在不同App中流轉,通過拖拽的方式可以更直觀地進行交互。
雙面屏互動玩法
外折疊屏在折疊狀態下可轉為雙面屏,等于是給用戶增加多一個觀看視角。例如華為 Mate X 的鏡像拍攝可以讓被拍攝者即時獲知自己的鏡頭影像是否滿意,這一拍女友神器有望成為直男拍攝終結者。在未來更多的多人觀看和互動玩法將被開拓出來。
△ 華為Mate X 的鏡像拍攝
未來隨著5G通訊技術的成長,越來越多的設備可以同時加入物聯網,人們的生活將被各種智能設備圍繞,設計師需要參與更多屏幕外的設計,讓不同設備串聯在一起協同合作,讓用戶能更加自在地享受科技的便利。
席卷全球的新冠疫情讓數十億用戶乖乖待在家里。過去需要花費大量精力去教育的用戶使用習慣因為疫情紛紛轉變。云購物、云蹦迪、云賞櫻、云監工……人們足不出戶便可還原許多線下場景。隨著用戶線上和線下生活的界限進一步模糊,用戶對于應用的效率和情感訴求也發生了變化。
疫情讓遠程辦公學習需求劇增,多人協作場景越來越頻繁,許多企業隨之升級了電話、視頻會議、文檔制作等多人協作效率軟件。過去僅僅考慮少人場景協作的方式不適用,設計師需要比以往更多地考慮多人協作場景下,如何對海量密集的信息進行分析處理和展示。
在學習方式上,由于線下學習轉移至線上,學生群體對于娛樂向軟件也有了效率訴求。為了順應用戶訴求變化,2020年5月QQ推出學習模式,屏蔽娛樂性的內容推送,讓學生更專注在學習上。
除了效率訴求急劇提升以外,隨著長時間的線上學習與辦公所產生社交疏離感和缺失感,人們對于線上學習工作的情感化訴求也進一步增強。
2020年推出的plagi遠程辦公軟件支持設置每個人的avartar形象,讓大家在遠程辦公時依舊能時刻感受到彼此的存在。在完成任務時還可以放鞭炮慶祝,讓員工能感受到親密無間的線上辦公體驗。
設計師需要更加關注如何讓線上生活進一步與現實生活圈和時間線接軌,通過拓展真實社交下的更多伴生行為讓線上也能還原線下的真實場景細節和互動體驗,以彌補用戶對真實社交的缺失感。
疫情的發生加速了人與信息之間的連接。人們越來越習慣將自身的身體資料、心情狀態等信息沉淀在智能設備上。
為了做好廣大市民群眾的健康監測服務,輔助疫情防控工作,微信和支付寶在2020年年初都上線了健康碼服務,不同顏色的健康碼代表人們不同的健康情況,市民出入特定場所都需初始健康碼。
隨著人的數據化越來越深入,個人身份信息的線上化在各平臺上將成為更加通用的能力。設計師需要考慮如何更自然更低成本地將線下動態變化的資料信息線上化,更有效地對用戶信息進行加工處理,以及記憶用戶的使用習慣和行為,以便幫助用戶更地完成任務。
疫情的出現加速了線下生活線上化,短短時間內我們看到日常習以為常的應用為響應疫情下的特殊需求紛紛出現改造,釘釘、QQ群被改造成上網課、批改作業的地方,醫療衛生公眾號開辟了實時疫情播報與辟謠通道,無接觸設計和服務需求異常突出……這也啟發了設計師需要保持對突發事件的敏感力以及應急能力,在日常生活中留心思考,為日后突發事件提供充足的場景支撐。
在洶涌的資本語境下,互聯網設計師裹挾在商業驅動的結果導向中狂奔,對設計的倫理和責任鮮有發聲,但伴隨著互聯網紅利退潮,發展放緩,狂奔之下的人本問題也逐漸浮出水面。在大趨勢下,UX設計師需要培養自身設計對倫理和責任的敏感度,在滿足商業目的外,重拾節操,為多群體,為大社會設計,更加注重「以人為本」。
包容性設計師指在做設計產品的時候,考慮到各類用戶的訴求,輸出具有包容性的設計方案。包容性設計依舊是2020年設計主題之一,伴隨著互聯網產品全球化,在通用性和包容性上也提出了新的要求。
為身障人士設計
三星在2019年針對東南亞市場推出了一款讓聾盲人士和健全人實時交流的app:Good Vibes,盲聾人輕擊屏幕輸入摩斯電碼,預先連線好的另一臺手機就會顯示從盲聾人發來的短信。健全人用普通的文字輸入回復,在盲聾人這一端就會翻譯成摩斯電碼、以手機振動的方式讀出短信內容。
GOOD VIBES宣傳視頻:https://v.qq.com/x/page/g3108sr8qc9.html
餓了么:在餓了么送貨騎手中,約8%受色盲色弱的困擾(全國男性群體中紅綠色盲色弱占比達8%-9%,餓了么騎手男性占比90%),為此餓了么設計團隊在2019年對app的進行了重新設計,包括使用WCAG無障礙色彩對比度,以及無障礙色盤,以及調整字階,使用輔助圖形等設計手段來解決部分騎手在送貨途中使用APP的痛點問題。
△ 餓了么UED:《為騎士創造平等 — 配送 App 的包容性設計》
跨年齡段設計
谷歌助手禮貌功能 ( Google Pretty Please ) :開啟谷歌助手禮貌功能后,如果使用者在下達指令的語句中包括「Please」,谷歌助手會對禮貌的請求表示感謝,以此培養孩子的禮貌言行。
Google Pretty Please功能宣傳:https://v.qq.com/x/page/e3108ue3e2t.html
Swift Playground:當10后小學生VITA君的編程課被「可敬的發量」刷滿彈幕時,Swift playgrounds功不可沒,這款為兒童新手學習編程的軟件,用趣味的游戲方式為4歲以上低齡用戶提供了一個學習編程的低門檻平臺。
為性別平等而設計
蘋果emoji:回看歷年蘋果emoji的更新,從膚色平等,到性別、性向平等,再到為殘疾人設計,2020年再為跨性別者增加新表情,性別平等依舊是包容性設計中重要一環。
Airbnb插畫:愛彼迎在插畫系統中,也為不同膚色,不同職業,不同性別,以及身障人士進行了人物的繪制。
2019是互聯網科技隱私問題沉浮的一年,國外有Facebook因泄露隱私收到史上最大罰單,國內則打響了「人臉識別第一案」?;\罩在隱私信任危機下,個人信息和數據立法突飛猛進,美國推動《加州消費者隱私法案》,國內也將在2020年出臺《個人信息保護法》和《數據安全法》。
MIUI12推出隱匿面具功能
Android開放生態導致的權限隱私問題一直被用戶所詬病,某些APP存在用戶不授權就無法使用情況,針對這一情況,MIUI12推出了隱匿面具功能。當用戶在開啟某些APP要求授權權限時,可以選擇空白通行證進行授權,從而保護用戶真實信息。
在MIUI12的更新中,還推出了照明彈、攔截網兩項隱私保護功能
iOS 14剪貼板提醒
在iOS 14的更新中,保護用戶隱私方面進一步升級。
其中剪貼板提醒設計很貼心,當用戶打開應用,如果該應用讀取了你剪貼板的內容,會在系統頂部彈出提示,用戶能在第一時間意識到剪貼板內容被讀取,幫助用戶更好的保護自己的隱私內容。
科技的發展是一把雙刃劍,互聯網產品的發展給用戶帶來便捷和沉浸體驗的同時,也使得用戶沉溺于科技所帶來的惰性和投食之下,逐漸喪失了對真實生活的把控權,被科技綁架。
數字福祉(digital wellbeing)近年被頻頻提起,指科技產品需要權衡好數碼產品和真實生活之間的平衡,防止數碼產品過渡分散用戶的注意力而影響生活質量。
Android Q 專注模式 Google Android Q Focus Mode
Android Q的更新加入了專注模式,用戶在專注模式下,可以在系統層面快捷地關閉使你分心的應用,讓你聚焦于更重要的事情。
防沉迷系統升級
推薦技術的進步,產品體驗的升級,給用戶帶來了更合胃口的菜式和沉浸體驗,但同時也被冠上了「電子海洛因」的稱號。游戲或者內容產品的防沉迷系統依舊會是數字福祉下不可避免的趨勢。
王者榮耀在2020年升級防沉迷系統,對青少年的娛樂時間和點券充值的限制進行了進一步升級。承接話。B站在2019年推出青少年模式,在該模式下,使用時長和內容推薦等做了定制化處理。
2020年的UI設計趨勢,一方面是對往年風格的衍變和細化,另一方面,在扁平克制的界面風格盛行后,設計師們向往更自由、更突破的視覺表達。
1. 深色模式
2019年iOS 13深色模式姍姍來遲,緊接著大廠APP相繼推出此功能。在2020年,深色模式會繼續普及外,也會在可視性和實現成本方面有更多細節打磨和研究。
2. 新擬態
設計趨勢的發展是螺旋式上升的,在扁平化設計流行之后,對物體的擬真再一次回歸設計圈,新擬態以一種對舊擬物風格的再創新,重新流行起來。
新擬物風格(Neumorphism)緣起于設計師Alexander Plyuto發布在dribbble的一組作品,以投影重新對扁平界面進行了塑造,模仿出類似浮雕的視覺效果,感受耳目一新,引起大量設計師相盡模仿。
新擬態的實用性和可落地性有待商榷,但是作為一種新的風格受到設計師擁躉,也不失為下一波風潮到來前的靈感繆斯。
WWDC2020對mac OS的更新也重新定義了新擬態設計語言,在mac OS新系統Big Sur中,圖標的設計增添了輕微的漸變、投影、高光,以此來營造圖標內元素之間的縱深關系。
3. 多彩配色
在扁平簡潔UI風格盛行之后,豐富的色彩依舊是設計趨勢之一,大面積色塊,碰撞配色,帶來更具沖擊感的視覺體驗。
4. 字體裝飾化
UI界面逐漸扁平,色塊圖標弱化,為突出頁面重心和內容,iOS 11在界面標題上使用更大的字號,更粗的字重。近年在大標題的風格衍變下,文字在傳達信息外,也開始有了裝飾性作用,采用超大字體,成為頁面排版美化的一部分。
5. 更大圓角
大圓角的風格會繼續延續,相較以往,卡片的處理圓角會更大,隨之帶來的是多的留白處理,結合大字號,帶來更透氣通透的視覺感受。
Mac OS Big Sur的界面相對舊版本采用了更大的圓角;系統圖標的設計統一成圓角矩形。
6. 更豐富的插圖
UI插圖的豐富體現在樣式和內容上,樣式上開始3D化,內容上更注重插圖敘事的表達。
7. 3D插圖
3D圖形往年更多運用在動態影像或運營類設計中,隨著3D的普及運用,UI插圖也會迎來3D化,給用戶帶來更立體,更新鮮的視覺感受。
8. 講求敘事表意
相較于往年追求形式的UI插圖,新趨勢下的插圖更講求功能性,每一副插圖都承載一定的作用——傳達功能信息或透傳品牌情感;同時插圖更講求畫面表意和情節,給用戶敘事性的視覺體驗,增進用戶和產品之間的情感聯系。
9. 插圖組件化
插畫的流行,隨之而來的是成本的水漲船高——一套系列插圖為保持風格統一,往往由唯一設計師繪制,同時為兼容各類場景,設計師往往要繪制多張。
為解決插圖的成本和效率,插圖開始以組件化的方式進行繪制——插圖設計師將插畫進行拆分繪制——不同人物,不同場景,不同物件等,再通過組件化的拼接合成,使用組件的設計師可以根據需求場景自由組合,也避免了風格不統一問題。
設計師Pablo Stanley將日常繪制的插畫制成一套矢量插圖組件庫,將人物分為:半身、全身和坐姿3大類。通過不同表情、發型和服裝可自由搭配出近60萬種組合。
Pablo Stanley人物插畫系統:https://v.qq.com/x/page/s3108yeyhmt.html
10. 多維度動畫表現
新趨勢下,動畫一方面回溯復古線描手繪風格,另一方面追求更三維的體驗,同時幀率進一步提升,追求更流暢的視覺感受。
11. 手繪動畫
手繪插圖是往年的熱門,其隨性自然的筆觸,能給用戶帶來親切的感受,在新的趨勢下,動畫的加入賦予手繪插圖一份靈性和趣味。
12. 3D運動
Google Material Design通過卡片投影層級和二維動畫規律,賦予扁平界面Z軸的縱深感。隨著3D的普及流行,新趨勢下的界面,界面的運動從二維走向三維,表現出3D場景下透視感。
13. 高幀率動畫
高幀率影視從線下電影院移步到線上流媒體,手機高幀率屏幕從90Hz到120Hz逐步升級,用戶對畫面流暢的定義一再刷新,UI動畫的幀率升級也會是新的一輪趨勢。
Telegram的表情采用了高幀率動畫,給用戶更流暢的視覺感受。
體驗的持續升級,產品的高速迭代,對UX設計師的設計師效率提出了更高的要求。的設計方式是一個永恒的趨勢。
1. 從本地文件到云端協作
傳統的文件交接方式效率低下,導致設計師之間信息不對稱,最終影響產品的一致性體驗。近些年在線設計協同工具發展迅速,從UI設計、 設計交付以及組件協同等環節上給設計師提供更加實時的協作體驗,獲得大量UX設計師的簇擁。在2019 uxtool的設計工具調研中,在線設計協同工具佼佼者figma以其協作和性能優勢,大有追趕sketch之勢。
隨著團隊對設計效率要求的提高,設計文檔從本地走向云端協作是不可逆趨勢。不過設計工具的迭代是需要成本的,尤其在大型設計團隊,設計工具需要渡過陣痛期來完成迭代,進而提升設計效率和體驗一致性。
2. 科學有效的設計系統
UX的發展,從早期的靜態規范到當下的動態設計系統,是為解決產品迭代增速后帶來的設計效率和產品體驗問題。商業驅動下的產品迭代速度有增無減,設計系統依舊會是未來幾年的設計趨勢之一。
這里說的設計系統不是廣義上的設計系統,而是在互聯網設計的發展中,對組件化設計逐步迭代升華的一套設計協作方法:
「設計系統(Design systems)是一組為了共同目標而服務的內在相互聯系的設計模式和多人協同執行的方法?!梗ㄒ浴禗esign systems》,Alla Kholmatova,C7210翻譯)。
3. 設計系統歷程衍變
組件化的發展歷經規范文檔到UI組件,再到設計系統,形態從最初對設計一致性的指導規范,到對產品研發流程的規范,以及產品設計價值觀的輸出,當下的設計系統以集大成者形式影響整個產品的設計形態。
設計系統的結構見下圖:
4. 設計系統的求同存異
設計系統并非一成不變的,他是一個動態進化的系統,會根據團隊性質、產品特性在內容上有所區分——比如大團隊更應該大而全,小團隊更傾向小而精;成熟產品的設計系統更傾向于打造完整閉環的合作流程機制,新產品的設計系統應該以小為始,快速迭代……
隨著產品的垂直化,細分化,設計系統的趨勢會是在趨勢大同之下找到適合產品和團隊自身的形態和節奏。
Material Design是一個包含了指導規范、組件,以及設計開發工具的自適應性設計系統。
它作為平臺型性設計系統,更為大而全的規范了整個生態系統的設計風格,以及提供工具讓研發者能快速產出符合規范的產品。
△ Google生態龐大繁雜,Material Design更為全面
Ant Design作為一個為to B產品提供解決方案的平臺,更多從設計可用性和完整性考慮設計系統的搭建。
△ Ant Design通過模塊化解決方案,降低冗余的生產成本,讓設計者專注于更好的用戶體驗
QQ作為一款面向95后的2C社交產品,其設計系統Q語言從風格調性上對設計進行規范,同時給予設計師一定的自由度;也考慮到QQ內兼顧多個產品,以及界面主題樣式,對基礎組件的使用場景和代碼進行了規范,方便設計和開發敏捷開發。
Q語言,給予產品的自由調性之外,也針對主題和基礎組件進行了規范。
每個產品和團隊都有自身的特征,設計系統的建設也應該有的放矢,沒有可照搬的標準答案,在大方向下找到適合自身的解決方案才是的可行之道,將效率最大化。
5. 科學有效的優化迭代
組件是設計系統中的重要組成部分,但是以往靜態的、孤立的協作方式使得組件的更新迭代滯后和阻塞。隨著設計系統的發展,設計師組件化思維的普及,組件的更新需要更科學的方式進行管理。
Figma在2019年推出的Design System Analytics功能,組件設計師可以借此查看組件的使用情況,包括引用次數,解組次數等,并可以生成組件使用情況的曲線趨勢圖,以數據的形式,科學地推動組件的優化迭代。
選擇分析的時間段;組件使用的次數曲線圖;團隊使用情況;所有組件使用情況
未來的用戶體驗會出現什么新趨勢?人工智能等算法的發展、5G技術普及、新的智能設備形態、新的信息處理技術、新一代用戶的喜好和口味……這些往后或將影響用戶體驗發展的走向。未來用戶對體驗的要求只會越來越高。
用戶體驗設計師需要了解更多的技術動向,但安身立命之本還是讓用戶真正受益:立足于用戶真實使用場景,在理性價值層面上,打造可用、易用、的設計;在感性需求層上賦予情感上的愉悅性,在反思層面賦予意義價值。
文章來源:優設 作者:騰訊ISUX
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
目錄
1. Animista
2. Animate CSS
3. Vivify
4. Magic Animations CSS3
5. cssanimation.io
6. Angrytools
7. Hover.css
8. WickedCSS
9. Three Dots
10. CSShake
1.Animista
網站地址:http://animista.net/
網站描述:在線生成 css 動畫
Animista是一個在線動畫生成器,同時也是一個動畫庫,它為我們提供了以下功能
1. 選擇不同的動畫
我們可以選擇想要的動畫類型(例如entrance/exist),除了可以選擇某個動畫(例如,scale-in)外,甚至還可以為該動畫選擇不同的展示效果(例如: scale-in-right)。
2. 定制
Animista還提供了一個功能,允許我們定制動畫的某些部分,比如
duration
delay
direction
更好的是,可以選擇要設置動畫的對象:
3. 生成CSS代碼
選擇適合自己需要的動畫后,我們可以直接從網站上獲取代碼,甚者可以進行壓縮:
4. 下載代碼
另一個好用的功能是,可以把自己收藏自己喜歡的動畫,然后一起下載下來, 或者,我們也可以選擇將這些動畫的代碼復制到一起。
2. Animate CSS
網站地址:http://daneden.github.io/anim...
網站描述:齊全的CSS3動畫庫
想必這個不用介紹,大部分人都知道了。Animate CSS 可能是最著名的動畫庫之一。這里簡要介紹一下它的用法:
1. 用法
首先,必須在總需要動畫元素上添加類animated ,然后是動畫的名字。
<div class="animated slideInLeft"></div>
如果我們想讓動畫一直持續,可以添加infinite類。
通過 JS 來添加動畫:
document.querySelector('.my-element').classList.add('animated', 'slideInLeft')
通過 JQ 來添加動畫:
$(".my-element").addClass("animated slideInLeft")
2. 其它功能
Animate CSS提供了一些基本的類來控制動畫的延遲和速度。
delay
可以添加 delay 類來延遲動畫的播放。
<div class="animated slideInLeft delay-{1-5}"><div>
speed
我們還可以通過添加如下列出的類之一來控制動畫速度。
類名 速度時間
show 2s
slower 3s
fast 800ms
faster 500ms
<div class="animated slideInLeft slow|slower|fast|faster"><div>
3. Vivify
網站地址: http://vivify.mkcreative.cz/
網站描述: 一個更加豐富css動畫庫
Vivify 是一個動畫庫,可以看作是Animate CSS的增強版。它們的工作方式完全相同,有Animate CSS的大多數類且還擴展了一些。
<div class="vivify slideInLeft"></div>
使用 JS 方式:
document.querySelector('.my-element').classList.add('vivify', 'slideInLeft')
使用 JQ 方式:
$(".my-element").addClass("vivify slideInLeft")
與Animate CSS一樣,Vivify 還提供了一些類來控制動畫的持續時間和延遲。
延遲和持續時間類在以下間隔中可用:
<div class="delay|duration-{100|150|200|250...1000|1250|1500|1750...10750}"></div>
4. Magic Animations CSS3
網站地址: https://www.minimamente.com/p...
網站描述: Magic CSS3 Animations 是 CSS3 動畫的包,伴有特殊的效果,用戶可以自由的在 web 項目中使用。
這個動畫庫有一些非常漂亮和流暢的動畫,特別是3D的。沒什么好說的,自己去嘗試。
<div class="magictime fadeIn"></div>
使用 JS 方式:
document.querySelector('.my-element').classList.add('magictime', 'fadeIn')
使用 JQ 方式:
$(".my-element").addClass("magictime fadeIn")
5. cssanimation.io
網站地址: http://cssanimation.io/index....
cssanimation.io是一大堆不同動畫的集合,總共大概有200個,這很強大。如果你連在這里都沒有找到你所需的動畫,那么在其它也將很難找到。
它的工作原理與 Animista 類似。例如,可以選擇一個動畫并直接從站點獲取代碼,或者也可以下載整個庫。
用法
將cssanimation {animation_name}添加到指定的元素上。
<div class="cssanimation fadeIn"></div>
使用 JS
document.querySelector('.my-element').classList.add('cssanimation','fadeIn')
使用 JQ
$(".my-element").addClass("cssanimation fadeIn")
還可以添加 infinite 類,這樣動畫就可以循環播放。
<div class="cssanimation fadeIn infinite"></div>
此外,cssanimation.io還為我們提供了動漫字母的功能。使用這個需要引入letteranimation.js文件,然后將le {animation_name}添加到我們的文本元素中。
<div class="cssanimation leSnake"></div>
要使字母按順序產生動畫,添加sequence類,要使它們隨機產生動畫,添加random類。
<div class="cssanimation leSnake {sequence|random}"></div>
Sequence
Random
6.Angrytools
網站地址: https://angrytools.com/css/an...
如果使用不同的生成器,Angrytools實際上是一個集合,其中還包括CSS動畫生成器。
它可能不像Animista那么復雜,但我覺得這個也很不錯。這個站點還提供了一些自定義動畫的特性,比如動畫的持續時間或延遲。
但是我喜歡的是,我們可以在其展示時間軸上添加自定義的keyframes,然后可以直接在其中編寫代碼。 另外,也可以編輯現有的。
當我們完成的時候,可以得到完整的動畫代碼,也可以下載它。
7.Hover.css
網站地址: http://ianlunn.github.io/Hover/
網站描述: 純CSS3鼠標滑過效果動畫庫
Hover.css是許多CSS動畫的集合,與上面的動畫不同,每次將元素懸停時都會觸發。
一組CSS3支持的懸停效果,可應用于鏈接、按鈕、徽標、SVG和特色圖像等。
** 用法
它非常簡單:只需將類的名稱添加到元素中,比如
<button class="hvr-fade">Hover me!</button>
8.WickedCSS
網站地址: http://kristofferandreasen.gi...
WickedCSS是一個小的CSS動畫庫,它沒有太多的動畫變體,但至少有很大的變化。 其中大多數是我們已經熟悉的基礎知識,但它們確實很干凈。
它的用法很簡單,只需將動畫的名稱添加到元素中即可。
<div class="bounceIn"></div>
** 使用 JS
document.querySelector('.my-element').classList.add('bounceIn')
** 使用 JQ
$(".my-element").addClass("bounceIn")
9.Three Dots
網站地址: https://nzbin.github.io/three...
Three Dots是一組CSS加載動畫,它由三個點組成,而這些點僅由單個元素組成。
** 用法
只需創建一個div元素,并添加動畫的名稱
<div class="dot-elastic"></div>
10.CSShake
網站地址: https://elrumordelaluz.github...
顧名思義,CSShake是一個CSS動畫庫,其中包含不同類型的震動動畫。
** 用法
將shake {animation name}添加到元素中。
<div class="shake shake-hard"></div>
使用 JS
document.querySelector('.my-element').classList.add('shake','shake-hard')
使用 JQ
$(".my-element").addClass("shake shake-hard")
人才們的 【三連】 就是小智不斷分享的最大動力,如果本篇博客有任何錯誤和建議,歡迎人才們留言,最后,謝謝大家的觀看。
代碼部署后可能存在的BUG沒法實時知道,事后為了解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。
原文:https://dev.to/weeb/10-of-the...
交流
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
蘋果WWDC 2020在6月23日通過線上形式召開,本次開發者大會沒有發布太多硬件產品。不過macOS的新版本macOS big Sur依舊引來了大波關注,坦誠的講這次版本更新屬實是很震驚,這可能是近10年蘋果第二次顛覆性的改變,上次還是2013年的iOS 7開始全面扁平化,從而引發了設計圈「扁平擬物大戰」。
從系統連續性上看AirPods可以在不同設備上無縫切換,不同設備直接可以復制粘貼等等,顯而易見的一件事是,蘋果啟動了macOS的「 iOS化」,所以回歸到設計上,這次的升級對設計語言上的影響概括來說有以下幾個方面:
逐步 iOS 化的 macOS
不知道大家有沒有這種感覺:以前從諾基亞(或Android手機)切換到iPhone,動機是cool~,漂亮的外觀讓人第一面就愛上了,恨不得馬上就拿起來把玩?,F在給你個選擇切換回Android手機,不管那款手機有多漂亮多美好看,心里第一句話很有可能是:「XXX手機很棒,但我已經離不開蘋果的生態了哦!」(我就是這種德行,嘗試了N次還是回來了哈哈哈哈哈。)
這大概就是蘋果在系統打通的層面下的功夫,更加注重生態的維護,在不同的設備間尋找更多場景產生更多聯系。從設計的層面來看,iOS化的macOS對iPhone用戶更加友好,有相同的操作習慣,不必在mac和iOS上來回的切換。其實在Material Design推行的過程中就有相似的做法,Chrome的全面MD化,讓整個設計語言貫穿在移動設備和個人電腦中。
1. 完全繼承iOS基因的控制中心/通知中心和dock
全新的control center,可以看的出繼承了iOS/iPad OS的語言,UI和交互幾乎是沒有任何變化,整合系統一致性的初衷是甚好,在當下這個情況,不得不說亮度和聲音的調節我更依賴于物理鍵盤(或touchBar),不太清楚硬整合在一起的理由(難不成是過度解讀了,也許人家就想繼承繼承)。
從蘋果對產品的終局目標來看,也許會在硬件上取消物理控制按鍵或者更加優化 touchBar 去操控必要的屬性(也有傳聞說這是在為觸控版本的 macOS 做準備)。
同樣iOS化的通知中心(講真,用戶實際的桌面未必這么干凈,真是擔心本身在電腦側使用頻率就不是那么高的通知中心被混亂的文件夾淹沒)
2. 進一步強調的沉浸式氛圍
全局設計中最令我欣喜的是menu Bar的更進一步的優化!我在sketch里模擬了下效果(非嚴謹模擬哦),大概得出的結論是加高了背景模糊值,減少了本身的填充透明度,達到了現在的效果。
感官上來看的話是一個微小的改動,但從層級整合的角度這是蘋果設計的一大提升,通過光影等自然世界的隱喻抽象設計來減少硬性層級劃分,也符合Alan Dye(蘋果用戶界面設計副總裁)提出的「深度,陰影和半透明性用于創建層次結構」。
除了壁紙以外,圖標是最引人矚目的變化啦!iOS化的圖標規格的約束不用多說了,還有就是「新擬態」的第一次亮相。新擬態出現一定意義上是對這個時代獲取信息時枯燥感的厭煩與反抗,舉個例子:就像當你日復一日的穿基本款T恤的時候一定向往印花T恤,不管你是多么的成熟有型哈哈哈哈哈。
△ 左圖為新擬態,右圖為MD
icon的設計植入了新的設計理念,更突出去表現現實環境下的真實狀態,注重光影和厚度的變化,尤其是光影。細看每一個icon,嚴格遵循同一個光源(正頂光)去設計。
但話說回來,很難講這套icon是否成功,有幾個明顯的弊端也暴露出來了:
1. 可用性與美感的平衡
新擬態更重注光感的細膩程度,但對信息的有效呈現打了一定的折扣,這塊蘋果權衡決策下放棄了些許的美感,增強了有效的信息傳遞。
△ 圖片來源dribbble
2. 統一性
對一個生態來講,最最理想的是每一個生活在內的成員要和諧的相處,新的擬態風格相比扁平時代的圖標顯然給第三方開發者增加了難度,這種難度極有可能對沒有太多設計資源但又想好好開發應用的創業團隊造成一定的打擊(換句話說催生了設計外包的工作也不錯哈哈哈哈哈)。
總的來說,優勢和弊端同時存在吧,啥事都沒有完美的不是么~下圖是這次改版里我相對還比較喜歡的幾組icon了。
布局結構的簡化處理
從官方的HIG介紹中,sidebar被重點提到了。透過去看,其實是布局的底層邏輯發生了變化,10.15的時候上下兩段左右分欄的方式會存在一定的誤區:全局action控制當前試圖的交互???
從層級關系上也可以勉勉強強講得通,但實在是太牽強了,特別是前進后退按鈕存在所有文件之上這個蜜汁布局居然維持了這么久。層級清晰的梳理后帶來的一大優勢就是視覺的復雜性被降低了,借助現在這種列表視圖,很大程度上前進后退都用不到了。
另外要提及的是設計師可以思考的問題,下一代模糊效果-漸進模糊。這種模擬現實生活的真實模糊包括陰影/反光/相互透出,同時要考慮光源/角度/環境等等,不再單純的只是黑色/透明度(有興趣的話可以在sketch里模擬下或者留言討論討論~)。
人腦是需要一個提示來識別物體,我們稱之為反饋設計,這是人機交互中非常重要的一個環節,這也是為什么許多按鈕仍帶有陰影的原因。但是,這版本的macOS工具欄圖標丟失了形狀以消除視覺復雜性,所以重塑后的反饋會鼓勵用戶去操作。
對于設計師來講,不要害怕不強調所有選項,并非每個按鈕都需要具有形狀。只要設計足夠的反饋一樣可以起到被點擊的效果,例如當用戶將鼠標懸停在其表面上時,可能會出現更多的色彩更有趣的動畫,并盡可能消除視覺上的復雜性。
這次升級除了設計上的優化外,safari的變化也足夠令人欣喜,可以快速通過 tab 知道當前網頁的內容的功能真是令人欣喜(雖然我記得之前Yandex瀏覽器就是這么做的哈哈哈哈)。
可以預知時間地點的apple map:
悄悄的說,感覺蘋果越來越像騰訊了,有些小微企業的創新功能一受用戶喜歡不是買過來就是抄過來,有一種壟斷的感覺,想想悲催的Alfred和workflow都是這種命運。
macOS big Sur 的升級可以說是蘋果對未來的進一步探索,站在設計師的角度,有幾個值得學習的點:
文章來源:優設 作者:Nana的設計錦囊
毫無疑問,JavaScript是Web開發中最流行的編程語言之一。無論您使用的是React,Vue還是Angular,都只是JavaScript。圍繞JS展開了廣泛而重要的生態系統,提供了無數的框架和庫,可幫助你更快地開發應用程序。
但是有時候最好退一步,嘗試了解如何在沒有庫的情況下做事??纯聪旅娴拇a片段,以優雅的方式解決簡單的問題,并在日常項目情況下使用這些知識或為編碼面試做準備。
1.反轉字符串
在此示例中,我們使用擴展運算符(…),Array的reverse方法和String的join方法來反轉給定的字符串。
const reverseString = string => [...string].reverse().join('');
// 例子
reverseString('javascript'); // 'tpircsavaj'
reverseString('good'); // 'doog'
2.計算數字的階乘
要計算給定數字的階乘,我們使用箭頭函數和嵌套三元運算符。
const factoriaOfNumber = number => number < 0 ? (() => {
throw new TypeError('No negative numbers please');
})()
: number <=1
? 1
: number * factoriaOfNumber(number -1);
// 例子
factoriaOfNumber(4); // 24
factoriaOfNumber(8); // 40320
3.將數字轉換為數字數組
在此示例中,我們使用擴展運算符(…),Array的map方法和 parseInt 函數將給定的數字轉換為一個單數的數組。
const convertToArray = number => [...`${number}`].map(el => parseInt(el));
// 例子
convertToArray(5678); // [5, 6, 7, 8]
convertToArray(123456789); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
4.檢查數字是否為2的冪
這很簡單。我們檢查該數字不是偽造的,并使用按位AND運算符(&)來確定數字是否為2的冪。
const isNumberPowerOfTwo = number => !!number && (number & (number - 1)) == 0;
// 例子
isNumberPowerOfTwo(100); // false
isNumberPowerOfTwo(128); // true
5.從對象創建鍵值對數組
在此示例中,我們使用Object中的keys方法和Array中的map方法來映射Object的鍵并創建鍵/值對數組。
const keyValuePairsToArray = object => Object.keys(object).map(el => [el, object[el]]);
// 例子
keyValuePairsToArray({ Better: 4, Programming: 2 });
// [ ['Better', 4], ['Programming', 2] ]
keyValuePairsToArray({ x: 1, y: 2, z: 3 });
// [ ['x', 1], ['y', 2], ['z', 3] ]
6.返回數組中的[Number]個最大元素
為了從數組中返回最大元素,我們使用了一個箭頭函數,該函數獲取數組和我們希望函數返回的元素數。我們使用擴展運算符(…)以及Array中的sort和slice方法。請注意,如果我們不提供第二個參數,則 number 的默認值為 1,因此僅返回一個最大元素。
const maxElementsFromArray = (array, number = 1) => [...array].sort((x, y) => y - x).slice(0, number);
// 例子
maxElementsFromArray([1,2,3,4,5]); // [5]
maxElementsFromArray([7,8,9,10,10],2); // [10, 10]
7.檢查數組中的所有元素是否相等
在這個簡短的示例中,我們使用Array中的every方法檢查數組中的所有元素是否相等。我們基本上檢查每個元素是否等于數組中的第一個元素。
const elementsAreEqual = array => array.every(el => el === array[0]);
// 例子
elementsAreEqual([9,8,7,6,5]); // false
elementsAreEqual([4,4,4,4,4]); // true
8.返回兩個數的平均值
在此示例中,我們使用了擴展運算符(…)和Array中的reduce方法來返回兩個給定數字或一個數組的平均值。
const averageOfTwoNumbers = (...numbers) => numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0) / numbers.length;
// 例子
averageOfTwoNumbers(...[6,7,8]); // 7
averageOfTwoNumbers(6,7,8,9); // 7.5
9.返回兩個或多個數字的總和
要返回兩個或多個給定數字或一個數組的總和,我們再次使用擴展運算符(…)和Array中的reduce方法。
const sumOfNumbers = (...array) => [...array].reduce((accumulator, currentValue) => accumulator + currentValue, 0);
// 例子
sumOfNumbers(5,6,7,8,9.10); // 45
sumOfNumbers(...[1,2,3,4,5,6,7,8,9,10]); // 50
10.返回數字數組的冪集
在最后一個示例中,我們要返回數字數組的冪集。因此,我們使用Array中的reduce,map和concat方法。
const powersetOfArray = array => array.reduce((accumulator, currentValue) => accumulator.concat(accumulator.map(el => [currentValue].concat(el))), [[]]);
// 例子
powersetOfArray([4, 2]); // [[], [4], [2], [2, 4]]
powersetOfArray([1, 2, 3]); /
// [[], [1], [2], [2, 1], [3], [3, 1], [3, 2], [3, 2, 1]]
如你所見,使用JavaScript和一些ES6魔術來解決這些任務并不總是困難的。
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
閱讀之前
基本上,在JavaScript中遍歷對象取決于對象是否可迭代。默認情況下,數組是可迭代的。map 和 forEach 包含在Array.prototype 中,因此我們無需考慮可迭代性。如果你想進一步學習,我推薦你看看什么是JavaScript中的可迭代對象!
什么是map()和forEach()?
map 和 forEach 是數組中的幫助器方法,可以輕松地在數組上循環。我們曾經像下面這樣循環遍歷一個數組,沒有任何輔助函數。
var array = ['1', '2', '3'];
for (var i = 0; i < array.length; i += 1) {
console.log(Number(array[i]));
}
// 1
// 2
// 3
自JavaScript時代開始以來,就一直存在 for 循環。它包含3個表達式:初始值,條件和最終表達式。
這是循環數組的經典方法。從ECMAScript 5開始,新功能似乎使我們更加快樂。
map
map 的作用與 for 循環完全相同,只是 map 會創建一個新數組,其結果是在調用數組中的每個元素上調用提供的函數。
它需要兩個參數:一個是稍后在調用 map 或 forEach 時調用的回調函數,另一個是回調函數被調用時使用的名為 thisArg 的上下文變量。
const arr = ['1', '2', '3'];
// 回調函數接受3個參數
// 數組的當前值作為第一個參數
// 當前值在數組中的位置作為第二個參數
// 原始源數組作為第三個參數
const cb = (str, i, origin) => {
console.log(`${i}: ${Number(str)} / ${origin}`);
};
arr.map(cb);
// 0: 1 / 1,2,3
// 1: 2 / 1,2,3
// 2: 3 / 1,2,3
回調函數可以如下使用。
arr.map((str) => { console.log(Number(str)); })
map 的結果不等于原始數組。
const arr = [1];
const new_arr = arr.map(d => d);
arr === new_arr; // false
你還可以將對象作為 thisArg 傳遞到map。
const obj = { name: 'Jane' };
[1].map(function() {
// { name: 'Jane' }
console.dir(this);
}, obj);
[1].map(() => {
// window
console.dir(this);
}, obj);
對象 obj 成為 map 的 thisArg。但是箭頭回調函數無法將 obj 作為其 thisArg。
這是因為箭頭函數與正常函數不同。
forEach
forEach 是數組的另一個循環函數,但 map 和 forEach 在使用中有所不同。map 和 forEach 可以使用兩個參數——回調函數和 thisArg,它們用作其 this。
const arr = ['1', '2', '3'];
// 回調函數接受3個參數
// 數組的當前值作為第一個參數
// 當前值在數組中的位置作為第二個參數
// 原始源數組作為第三個參數
const cb = (str, i, origin) => {
console.log(`${i}: ${Number(str)} / ${origin}`);
};
arr.forEach(cb);
// 0: 1 / 1,2,3
// 1: 2 / 1,2,3
// 2: 3 / 1,2,3
那有什么不同?
map 返回其原始數組的新數組,但是 forEach 卻沒有。但是它們都確保了原始對象的不變性。
[1,2,3].map(d => d + 1); // [2, 3, 4];
[1,2,3].forEach(d => d + 1); // undefined;
如果更改數組內的值,forEach 不能確保數組的不變性。這個方法只有在你不接觸里面的任何值時,才能保證不變性。
[{a: 1, b: 2}, {a: 10, b: 20}].forEach((obj) => obj.a += 1);
// [{a: 2, b: 2}, {a: 11, b: 21}]
// 數組已更改!
何時使用map()和forEach()?
由于它們之間的主要區別在于是否有返回值,所以你會希望使用 map 來制作一個新的數組,而使用 forEach 只是為了映射到數組上。
這是一個簡單的例子。
const people = [
{ name: 'Josh', whatCanDo: 'painting' },
{ name: 'Lay', whatCanDo: 'security' },
{ name: 'Ralph', whatCanDo: 'cleaning' }
];
function makeWorkers(people) {
return people.map((person) => {
const { name, whatCanDo } = person;
return <li key={name}>My name is {name}, I can do {whatCanDo}</li>
});
}
<ul>makeWorkers(people)</ul>
比如在React中,map 是非常常用的制作元素的方法,因為 map 在對原數組的數據進行操作后,會創建并返回一個新的數組。
const mySubjectId = ['154', '773', '245'];
function countSubjects(subjects) {
let cnt = 0;
subjects.forEach(subject => {
if (mySubjectId.includes(subject.id)) {
cnt += 1;
}
});
return cnt;
}
countSubjects([
{ id: '223', teacher: 'Mark' },
{ id: '154', teacher: 'Linda' }
]);
// 1
另一方面,當你想對數據進行某些操作而不創建新數組時,forEach 很有用。順便說一句,可以使用 filter 重構示例。
subjects.filter(subject => mySubjectId.includes(subject.id)).length;
綜上所述,我建議你在創建一個新的數組時使用map,當你不需要制作一個新的數組,而是要對數據做一些事情時,就使用forEach。
速度比較
有些帖子提到 map 比 forEach 快。所以,我很好奇這是不是真的。我找到了這個對比結果。
該代碼看起來非常相似,但結果卻相反。有些測試說 forEach 更快,有些說 map 更快。也許你在告訴自己 map/forEach 比其他的快,你可能是對的。老實說,我不確定。我認為在現代Web開發中,可讀性比 map 和 forEach 之間的速度重要得多。
但可以肯定的是——兩者都比JavaScript內置的 for 循環慢。
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
序言
之前發表了一篇文章 redux、mobx、concent特性大比拼, 看后生如何對局前輩,吸引了不少感興趣的小伙伴入群開始了解和使用 concent,并獲得了很多正向的反饋,實實在在的幫助他們提高了開發體驗,群里人數雖然還很少,但大家熱情高漲,技術討論氛圍濃厚,對很多新鮮技術都有保持一定的敏感度,如上個月開始逐漸被提及得越來越多的出自facebook的狀態管理方案 recoil,雖然還處于實驗狀態,但是相必大家已經私底下開始欲欲躍試了,畢竟出生名門,有fb背書,一定會大放異彩。
不過當我體驗完recoil后,我對其中標榜的更新保持了懷疑態度,有一些誤導的嫌疑,這一點下文會單獨分析,是否屬于誤導讀者在讀完本文后自然可以得出結論,總之本文主要是分析Concent與Recoil的代碼風格差異性,并探討它們對我們將來的開發模式有何新的影響,以及思維上需要做什么樣的轉變。
數據流方案之3大流派
目前主流的數據流方案按形態都可以劃分以下這三類
redux流派
redux、和基于redux衍生的其他作品,以及類似redux思路的作品,代表作有dva、rematch等等。
mobx流派
借助definePerperty和Proxy完成數據劫持,從而達到響應式編程目的的代表,類mobx的作品也有不少,如dob等。
Context流派
這里的Context指的是react自帶的Context api,基于Context api打造的數據流方案通常主打輕量、易用、概覽少,代表作品有unstated、constate等,大多數作品的核心代碼可能不超過500行。
到此我們看看Recoil應該屬于哪一類?很顯然按其特征屬于Context流派,那么我們上面說的主打輕量對
Recoil并不適用了,打開其源碼庫發現代碼并不是幾百行完事的,所以基于Context api做得好用且強大就未必輕量,由此看出facebook對Recoil是有野心并給予厚望的。
我們同時也看看Concent屬于哪一類呢?Concent在v2版本之后,重構數據追蹤機制,啟用了defineProperty和Proxy特性,得以讓react應用既保留了不可變的追求,又享受到了運行時依賴收集和ui更新的性能提升福利,既然啟用了defineProperty和Proxy,那么看起來Concent應該屬于mobx流派?
事實上Concent屬于一種全新的流派,不依賴react的Context api,不破壞react組件本身的形態,保持追求不可變的哲學,僅在react自身的渲染調度機制之上建立一層邏輯層狀態分發調度機制,defineProperty和Proxy只是用于輔助收集實例和衍生數據對模塊數據的依賴,而修改數據入口還是setState(或基于setState封裝的dispatch, invoke, sync),讓Concent可以0入侵的接入react應用,真正的即插即用和無感知接入。
即插即用的核心原理是,Concent自建了一個平行于react運行時的全局上下文,精心維護這模塊與實例之間的歸屬關系,同時接管了組件實例的更新入口setState,保留原始的setState為reactSetState,所有當用戶調用setState時,concent除了調用reactSetState更新當前實例ui,同時智能判斷提交的狀態是否也還有別的實例關心其變化,然后一并拿出來依次執行這些實例的reactSetState,進而達到了狀態全部同步的目的。
Recoil初體驗
我們以常用的counter來舉例,熟悉一下Recoil暴露的四個高頻使用的api
atom,定義狀態
selector, 定義派生數據
useRecoilState,消費狀態
useRecoilValue,消費派生數據
定義狀態
外部使用atom接口,定義一個key為num,初始值為0的狀態
const numState = atom({
key: "num",
default: 0
});
定義派生數據
外部使用selector接口,定義一個key為numx10,初始值是依賴numState再次計算而得到
const numx10Val = selector({
key: "numx10",
get: ({ get }) => {
const num = get(numState);
return num * 10;
}
});
定義異步的派生數據
selector的get支持定義異步函數
需要注意的點是,如果有依賴,必需先書寫好依賴在開始執行異步邏輯
const delay = () => new Promise(r => setTimeout(r, 1000));
const asyncNumx10Val = selector({
key: "asyncNumx10",
get: async ({ get }) => {
// !!!這句話不能放在delay之下, selector需要同步的確定依賴
const num = get(numState);
await delay();
return num * 10;
}
});
消費狀態
組件里使用useRecoilState接口,傳入想要獲去的狀態(由atom創建而得)
const NumView = () => {
const [num, setNum] = useRecoilState(numState);
const add = ()=>setNum(num+1);
return (
<div>
{num}<br/>
<button onClick={add}>add</button>
</div>
);
}
消費派生數據
組件里使用useRecoilValue接口,傳入想要獲去的派生數據(由selector創建而得),同步派生數據和異步派生數據,皆可通過此接口獲得
const NumValView = () => {
const numx10 = useRecoilValue(numx10Val);
const asyncNumx10 = useRecoilValue(asyncNumx10Val);
return (
<div>
numx10 :{numx10}<br/>
</div>
);
};
渲染它們查看結果
暴露定義好的這兩個組件, 查看在線示例
export default ()=>{
return (
<>
<NumView />
<NumValView />
</>
);
};
頂層節點包裹React.Suspense和RecoilRoot,前者用于配合異步計算函數需要,后者用于注入Recoil上下文
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<React.Suspense fallback={<div>Loading...</div>}>
<RecoilRoot>
<Demo />
</RecoilRoot>
</React.Suspense>
</React.StrictMode>,
rootElement
);
Concent初體驗
如果讀過concent文檔(還在持續建設中...),可能部分人會認為api太多,難于記住,其實大部分都是可選的語法糖,我們以counter為例,只需要使用到以下兩個api即可
run,定義模塊狀態(必需)、模塊計算(可選)、模塊觀察(可選)
運行run接口后,會生成一份concent全局上下文
setState,修改狀態
定義狀態&修改狀態
以下示例我們先脫離ui,直接完成定義狀態&修改狀態的目的
import { run, setState, getState } from "concent";
run({
counter: {// 聲明一個counter模塊
state: { num: 1 }, // 定義狀態
}
});
console.log(getState('counter').num);// log: 1
setState('counter', {num:10});// 修改counter模塊的num值為10
console.log(getState('counter').num);// log: 10
我們可以看到,此處和redux很類似,需要定義一個單一的狀態樹,同時第一層key就引導用戶將數據模塊化管理起來.
引入reducer
上述示例中我們直接掉一個呢setState修改數據,但是真實的情況是數據落地前有很多同步的或者異步的業務邏輯操作,所以我們對模塊填在reducer定義,用來聲明修改數據的方法集合。
import { run, dispatch, getState } from "concent";
const delay = () => new Promise(r => setTimeout(r, 1000));
const state = () => ({ num: 1 });// 狀態聲明
const reducer = {// reducer聲明
inc(payload, moduleState) {
return { num: moduleState.num + 1 };
},
async asyncInc(payload, moduleState) {
await delay();
return { num: moduleState.num + 1 };
}
};
run({
counter: { state, reducer }
});
然后我們用dispatch來觸發修改狀態的方法
因dispatch會返回一個Promise,所以我們需要用一個async 包裹起來執行代碼
import { dispatch } from "concent";
(async ()=>{
console.log(getState("counter").num);// log 1
await dispatch("counter/inc");// 同步修改
console.log(getState("counter").num);// log 2
await dispatch("counter/asyncInc");// 異步修改
console.log(getState("counter").num);// log 3
})()
注意dispatch調用時基于字符串匹配方式,之所以保留這樣的調用方式是為了照顧需要動態調用的場景,其實更推薦的寫法是
import { dispatch } from "concent";
(async ()=>{
console.log(getState("counter").num);// log 1
await dispatch(reducer.inc);// 同步修改
console.log(getState("counter").num);// log 2
await dispatch(reducer.asyncInc);// 異步修改
console.log(getState("counter").num);// log 3
})()
接入react
上述示例主要演示了如何定義狀態和修改狀態,那么接下來我們需要用到以下兩個api來幫助react組件生成實例上下文(等同于與vue 3 setup里提到的渲染上下文),以及獲得消費concent模塊數據的能力
register, 注冊類組件為concent組件
useConcent, 注冊函數組件為concent組件
import { register, useConcent } from "concent";
@register("counter")
class ClsComp extends React.Component {
changeNum = () => this.setState({ num: 10 })
render() {
return (
<div>
<h1>class comp: {this.state.num}</h1>
<button onClick={this.changeNum}>changeNum</button>
</div>
);
}
}
function FnComp() {
const { state, setState } = useConcent("counter");
const changeNum = () => setState({ num: 20 });
return (
<div>
<h1>fn comp: {state.num}</h1>
<button onClick={changeNum}>changeNum</button>
</div>
);
}
注意到兩種寫法區別很小,除了組件的定義方式不一樣,其實渲染邏輯和數據來源都一模一樣。
渲染它們查看結果
在線示例
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<div>
<ClsComp />
<FnComp />
</div>
</React.StrictMode>,
rootElement
);
對比Recoil,我們發現沒有頂層并沒有Provider或者Root類似的組件包裹,react組件就已接入concent,做到真正的即插即用和無感知接入,同時api保留為與react一致的寫法。
組件調用reducer
concent為每一個組件實例都生成了實例上下文,方便用戶直接通過ctx.mr調用reducer方法
mr 為 moduleReducer的簡寫,直接書寫為ctx.moduleReducer也是合法的
// --------- 對于類組件 -----------
changeNum = () => this.setState({ num: 10 })
// ===> 修改為
changeNum = () => this.ctx.mr.inc(10);// or this.ctx.mr.asynCtx()
// --------- 對于函數組件 -----------
const { state, mr } = useConcent("counter");// useConcent 返回的就是ctx
const changeNum = () => mr.inc(20);// or ctx.mr.asynCtx()
異步計算函數
run接口里支持擴展computed屬性,即讓用戶定義一堆衍生數據的計算函數集合,它們可以是同步的也可以是異步的,同時支持一個函數用另一個函數的輸出作為輸入來做二次計算,計算的輸入依賴是自動收集到的。
const computed = {// 定義計算函數集合
numx10({ num }) {
return num * 10;
},
// n:newState, o:oldState, f:fnCtx
// 結構出num,表示當前計算依賴是num,僅當num發生變化時觸發此函數重計算
async numx10_2({ num }, o, f) {
// 必需調用setInitialVal給numx10_2一個初始值,
// 該函數僅在初次computed觸發時執行一次
f.setInitialVal(num * 55);
await delay();
return num * 100;
},
async numx10_3({ num }, o, f) {
f.setInitialVal(num * 1);
await delay();
// 使用numx10_2再次計算
const ret = num * f.cuVal.numx10_2;
if (ret % 40000 === 0) throw new Error("-->mock error");
return ret;
}
}
// 配置到counter模塊
run({
counter: { state, reducer, computed }
});
上述計算函數里,我們刻意讓numx10_3在某個時候報錯,對于此錯誤,我們可以在run接口的第二位options配置里定義errorHandler來捕捉。
run({/**storeConfig*/}, {
errorHandler: (err)=>{
alert(err.message);
}
})
當然更好的做法,利用concent-plugin-async-computed-status插件來完成對所有模塊計算函數執行狀態的統一管理。
import cuStatusPlugin from "concent-plugin-async-computed-status";
run(
{/**storeConfig*/},
{
errorHandler: err => {
console.error('errorHandler ', err);
// alert(err.message);
},
plugins: [cuStatusPlugin], // 配置異步計算函數執行狀態管理插件
}
);
該插件會自動向concent配置一個cuStatus模塊,方便組件連接到它,消費相關計算函數的執行狀態數據
function Test() {
const { moduleComputed, connectedState, setState, state, ccUniqueKey } = useConcent({
module: "counter",// 屬于counter模塊,狀態直接從state獲得
connect: ["cuStatus"],// 連接到cuStatus模塊,狀態從connectedState.{$moduleName}獲得
});
const changeNum = () => setState({ num: state.num + 1 });
// 獲得counter模塊的計算函數執行狀態
const counterCuStatus = connectedState.cuStatus.counter;
// 當然,可以更細粒度的獲得指定結算函數的執行狀態
// const {['counter/numx10_2']:num1Status, ['counter/numx10_3']: num2Status} = connectedState.cuStatus;
return (
<div>
{state.num}
<br />
{counterCuStatus.done ? moduleComputed.numx10 : 'computing'}
{/** 此處拿到錯誤可以用于渲染,當然也拋出去 */}
{/** 讓ErrorBoundary之類的組件捕捉并渲染降級頁面 */}
{counterCuStatus.err ? counterCuStatus.err.message : ''}
<br />
{moduleComputed.numx10_2}
<br />
{moduleComputed.numx10_3}
<br />
<button onClick={changeNum}>changeNum</button>
</div>
);
}
![]https://raw.githubusercontent...
查看在線示例
更新
開篇我說對Recoli提到的更新保持了懷疑態度,有一些誤導的嫌疑,此處我們將揭開疑團
大家知道hook使用規則是不能寫在條件控制語句里的,這意味著下面語句是不允許的
const NumView = () => {
const [show, setShow] = useState(true);
if(show){// error
const [num, setNum] = useRecoilState(numState);
}
}
所以用戶如果ui渲染里如果某個狀態用不到此數據時,某處改變了num值依然會觸發NumView重渲染,但是concent的實例上下文里取出來的state和moduleComputed是一個Proxy對象,是在實時的收集每一輪渲染所需要的依賴,這才是真正意義上的按需渲染和更新。
const NumView = () => {
const [show, setShow] = useState(true);
const {state} = useConcent('counter');
// show為true時,當前實例的渲染對state.num的渲染有依賴
return {show ? <h1>{state.num}</h1> : 'nothing'}
}
點我查看代碼示例
當然如果用戶對num值有ui渲染完畢后,有發生改變時需要做其他事的需求,類似useEffect的效果,concent也支持用戶將其抽到setup里,定義effect來完成此場景訴求,相比useEffect,setup里的ctx.effect只需定義一次,同時只需傳遞key名稱,concent會自動對比前一刻和當前刻的值來決定是否要觸發副作用函數。
conset setup = (ctx)=>{
ctx.effect(()=>{
console.log('do something when num changed');
return ()=>console.log('clear up');
}, ['num'])
}
function Test1(){
useConcent({module:'cunter', setup});
return <h1>for setup<h1/>
}
更多關于effect與useEffect請查看此文
current mode
關于concent是否支持current mode這個疑問呢,這里先說答案,concent是100%完全支持的,或者進一步說,所有狀態管理工具,最終觸發的都是setState或forceUpdate,我們只要在渲染過程中不要寫具有任何副作用的代碼,讓相同的狀態輸入得到的渲染結果冪,即是在current mode下運行安全的代碼。
current mode只是對我們的代碼提出了更苛刻的要求。
// bad
function Test(){
track.upload('renderTrigger');// 上報渲染觸發事件
return <h1>bad case</h1>
}
// good
function Test(){
useEffect(()=>{
// 就算僅執行了一次setState, current mode下該組件可能會重復渲染,
// 但react內部會保證該副作用只觸發一次
track.upload('renderTrigger');
})
return <h1>bad case</h1>
}
我們首先要理解current mode原理是因為fiber架構模擬出了和整個渲染堆棧(即fiber node上存儲的信息),得以有機會讓react自己以組件為單位調度組件的渲染過程,可以懸停并再次進入渲染,安排優先級高的先渲染,重度渲染的組件會切片為多個時間段反復渲染,而concent的上下文本身是獨立于react存在的(接入concent不需要再頂層包裹任何Provider), 只負責處理業務生成新的數據,然后按需派發給對應的實例(實例的狀態本身是一個個孤島,concent只負責同步建立起了依賴的store的數據),之后就是react自己的調度流程,修改狀態的函數并不會因為組件反復重入而多次執行(這點需要我們遵循不該在渲染過程中書寫包含有副作用的代碼原則),react僅僅是調度組件的渲染時機,而組件的中斷和重入針對也是這個渲染過程。
所以同樣的,對于concent
const setup = (ctx)=>{
ctx.effect(()=>{
// effect是對useEffect的封裝,
// 同樣在current mode下該副作用也只觸發一次(由react保證)
track.upload('renderTrigger');
});
}
// good
function Test2(){
useConcent({setup})
return <h1>good case</h1>
}
同樣的,依賴收集在current mode模式下,重復渲染僅僅是導致觸發了多次收集,只要狀態輸入一樣,渲染結果冪等,收集到的依賴結果也是冪等的。
// 假設這是一個渲染很耗時的組件,在current mode模式下可能會被中斷渲染
function HeavyComp(){
const { state } = useConcent({module:'counter'});// 屬于counter模塊
// 這里讀取了num 和 numBig兩個值,收集到了依賴
// 即當僅當counter模塊的num、numBig的發生變化時,才觸發其重渲染(最終還是調用setState)
// 而counter模塊的其他值發生變化時,不會觸發該實例的setState
return (
<div>num: {state.num} numBig: {state.numBig}</div>
);
}
最后我們可以梳理一下,hook本身是支持把邏輯剝離到用的自定義hook(無ui返回的函數),而其他狀態管理也只是多做了一層工作,引導用戶把邏輯剝離到它們的規則之下,最終還是把業務處理數據交回給react組件調用其setState或forceUpdate觸發重渲染,current mode的引入并不會對現有的狀態管理或者新生的狀態管理方案有任何影響,僅僅是對用戶的ui代碼提出了更高的要求,以免因為current mode引發難以排除的bug
為此react還特別提供了React.Strict組件來故意觸發雙調用機制, https://reactjs.org/docs/stri... 以引導用戶書寫更符合規范的react代碼,以便適配將來提供的current mode。
react所有新特性其實都是被fiber激活了,有了fiber架構,衍生出了hook、time slicing、suspense以及將來的Concurrent Mode,class組件和function組件都可以在Concurrent Mode下安全工作,只要遵循規范即可。
摘取自: https://reactjs.org/docs/stri...
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
所以呢,React.Strict其實為了引導用戶寫能夠在Concurrent Mode里運行的代碼而提供的輔助api,先讓用戶慢慢習慣這些限制,循序漸進一步一步來,最后再推出Concurrent Mode。
結語
Recoil推崇狀態和派生數據更細粒度控制,寫法上demo看起來簡單,實際上代碼規模大之后依然很繁瑣。
// 定義狀態
const numState = atom({key:'num', default:0});
const numBigState = atom({key:'numBig', default:100});
// 定義衍生數據
const numx2Val = selector({
key: "numx2",
get: ({ get }) => get(numState) * 2,
});
const numBigx2Val = selector({
key: "numBigx2",
get: ({ get }) => get(numBigState) * 2,
});
const numSumBigVal = selector({
key: "numSumBig",
get: ({ get }) => get(numState) + get(numBigState),
});
// ---> ui處消費狀態或衍生數據
const [num] = useRecoilState(numState);
const [numBig] = useRecoilState(numBigState);
const numx2 = useRecoilValue(numx2Val);
const numBigx2 = useRecoilValue(numBigx2Val);
const numSumBig = useRecoilValue(numSumBigVal);
Concent遵循redux單一狀態樹的本質,推崇模塊化管理數據以及派生數據,同時依靠Proxy能力完成了運行時依賴收集和追求不可變的完美整合。
run({
counter: {// 聲明一個counter模塊
state: { num: 1, numBig: 100 }, // 定義狀態
computed:{// 定義計算,參數列表里解構具體的狀態時確定了依賴
numx2: ({num})=> num * 2,
numBigx2: ({numBig})=> numBig * 2,
numSumBig: ({num, numBig})=> num + numBig,
}
},
});
// ---> ui處消費狀態或衍生數據,在ui處結構了才產生依賴
const { state, moduleComputed, setState } = useConcent('counter')
const { numx2, numBigx2, numSumBig} = moduleComputed;
const { num, numBig } = state;
所以你將獲得:
運行時的依賴收集 ,同時也遵循react不可變的原則
一切皆函數(state, reducer, computed, watch, event...),能獲得更友好的ts支持
支持中間件和插件機制,很容易兼容redux生態
同時支持集中與分形模塊配置,同步與異步模塊加載,對大型工程的彈性重構過程更加友好
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
近年來暗黑模式的設計趨勢開始一點點明顯,Ant Design 在這次 4.0 的升級中也對這類暗黑場景化的設計開始進行初步的探索,接下來就讓我們一起來看下 Ant Design 這一針對企業級的設計體系是如何設計暗黑模式的。
暗黑模式是指把所有 UI 換成黑色或者深色的一個模式。
需要說明的是,暗黑模式不只夜間模式:
暗黑模式更多的目的是希望使用者更加專注自己的操作任務,所以對于信息內容的表達會更注重視覺性;
而夜間模式則更多是出于在夜間或暗光環境使用下的健康角度考慮,避免在黑暗環境中長時間注視高亮光源帶來的視覺刺激,所以在保證可讀性的前提下,會采用更弱的對比來減少屏幕光對眼睛的刺激。
同時,從使用場景上來說,暗黑模式既可以在黑暗環境,也可以在亮光環境下使用,可以理解為是對淺色主題的一種場景化補充,而夜間模式只建議在黑暗環境下使用,在亮光環境使用時很可能并不保證信息可讀性。
1. 更加專注內容
試想一下,我們在電影院看電影時,為什么要全場關燈?甚至有些 APP,在影片的下方也會又一個模擬關燈效果的操作,來讓整個手機屏幕變黑,只剩下視屏畫面的部分,這都幫助我們可以更專注、更沉浸在當前的內容下。
色彩具有層級關系,深色會在視覺感官上自動后退,淺色部分則會向前延展,這樣對比強烈的層次關系可以讓用戶更注重被凸顯出來的內容和交互操作;尤其在信息負責界面內層級關系的合理拉開對操作效率都有明顯的促進作用。
2. 在暗光環境下更加適用
如今社會我們身處黑夜使用手機、電腦、iPad等設備的次數越來越多,環境光與屏幕亮度的明暗差距在夜間會被放大 ,亮度對比帶來視覺刺激也更加明顯,使用暗色模式可以縮小屏幕顯示內容與環境光強度的差距,同時也可以為設備的續航帶來積極影響,可以保證使用者在暗光環境下使用 OLED 設備的舒適度。
3. 大眾喜愛
黑色一直以來就可以給人以高級、神秘的語義象征,相比于淺色模式,暗色模式藏著更多可能性。
在這次暗黑模式的設計中主要遵循以下兩大設計原則:
1. 內容的舒適性
不論是顏色還是文字或是組件本身,在暗色環境下的使用感受應當是舒適的,而不是十分費力的,如果一個顏色在淺色下使用正常,在暗色下卻亮的刺眼或根本看不見,那必然不夠舒適也不可讀;所以在顏色的處理上不建議直接使用,這樣會讓界面變得到處都是「亮點」,讓眼睛不適的同時,也會帶來許多誤操作。
2. 信息的一致性
暗黑模式下的信息內容需要和淺色模式保持一致性,不應該打破原有的層級關系。舉個例子,在淺色模式下越深的顏色,與界面背景色對比度越大,也就越容易被人注意,視覺層級越高,比如 tooltip;在暗黑模式下我們同樣需要遵循這一規律,所以對應所使用的顏色也就越淺,反之則會越深。
在大量的企業級產品界面中,我們通常用只用一個白色背景結合分割線就可以搞定所有界面的板塊層級,因為在淺色模式下有投影可以借助,然而暗黑模式中投影將不足以起到如此功效,我們需要通過顏色來區分層級關系。
在經過對螞蟻企業級頁面的典型布局結構評估后,我們在中性色中增加了三個梯度,將中性色擴展至 13 個
并定義出通用情況下頁面中的框架層次,主要分為三大塊:
在目前的暗黑體系下,我們分別為這三大塊從淺到深定義了#1F1F1F、#141414、#000000 三個顏色,在實際應用中,你也可以根據自身業務的需求,從中性色板中直接選用或是依據透明度的思路自定義出合適的中性色彩。當定義出較為明確的框架層次和顏色后,也對后續系統中組件的顏色配置有著重要的指導意義。我們需要考慮組件出現在不同顏色背景下的可能性及其表現,盡量保持一致性。
眾所周知,暗黑模式與淺色模式最大的不同就在色彩的處理上,在暗黑模式中,我們并不想打破淺色模式下基礎色板的配置規律及色值,當一個應用或站點深淺模式并存時,希望在色彩上有一定延續和關聯,而不是毫不相關的兩套色板,這樣一是避免開發及后續的維護成本,二是實際切換和使用時,可以保證一致性,這意味著需要借助一定規則。
這里分享一下我們的處理思路:
基于 Ant Design 自然的設計價值觀,我們先從自然界中尋找靈感,如果說淺色模式如同初升時的朝陽,那暗黑模式就是落日下的晚霞,各有各的韻味,同一片天,唯一不同的是,受光線亮度的影響,晚霞整體會暗一些。
所以我們大體的設計思路也是基于淺色的基礎色板,通過結合透明度的轉換,去得到一套暗黑模式下的色彩。這樣的好處是,深淺模式下的色彩根基是同一個,在這樣的基礎上經過透明度的變換得到的結果也會相對和諧,同時也符合我們一致性的原則。
這里我們借助下面這兩個概念對透明度進行轉換:
對比度極性
對比度極性分為正極性和負極性。
這里可以給大家分享對比度查閱的一個工具:WebAIM,只要輸入色值就可以看到具體的值,十分方便。
正負極性差值
顧名思義便是正負兩者的差值,這里取的是絕對值。
根據一致性原則,我們嘗試通過對比一套顏色的正負極性變化趨勢來找到轉換規律。
首先可以看下,如果一個顏色在不做任何修改的前提下直接使用,它的正負極性趨勢以及差值趨勢的走勢和關系是怎么樣的,我們嘗試描繪出這樣的曲線,他們的變化規律也將作為我們規則轉換的參考標準。
經過對比,可以發現一些變化規律:
首先來說下「差值趨勢」,橫向對比可以發現,不同顏色的正負極性走勢是很不一樣的,可以看到在黃綠色段差值曲線達到一個變化峰值,這是由于黃綠色本身由于明度、飽和度值相比其他顏色偏高,所以總是有種刺眼的感覺,生活中也會用它來作為警示、提醒的作用,所以在深淺背景下的對比度有一個比較大的差異,可以說這個變化是正常的。
這點也可以從「正負對比度極性趨勢」兩者間的相對關系反應出來,從紅色到洋紅,綠線一開始從逐漸在藍線的上方一點,開始逐漸上移,最后在峰值處開始慢慢下移,在「極客藍」這個色相中接近重疊,在洋紅處又慢慢下移,說明淺色下越深的顏色,在深色中越亮,反之則越暗。
縱向比對單個色板,可以發現,其斜率變化主要受顏色的明度、飽和度影響,可以反應一個顏色的不同梯度在黑白背景下的變化規律。
有了這個大的變化規律,我們便可做到心中有數。首先以 Ant Design 的品牌色「破曉藍」為例,經過在多個業務、場景中不斷嘗試與調整,我們得到一個透明度轉換規則:
并將這個規則應用在其他 11 套色板中,驗證其可用性。這個過程確實沒有什么快捷通道,唯一的辦法就是不斷嘗試。
最后,我們將經過規則轉換的實色與常規顏色的變化趨勢做對比:
可以看到在大趨勢走向上左右兩側圖基本一致,這代表兩個色板在變化規律接近一致,基本可以證明規則的合理性。區別在于,對比度負極性和差值相對于右側未經處理的值明顯有所下降。這是由于透明度的處理讓暗色下的顏色在明度、飽和度上有所下降導致。
再仔細觀察可以發現,在左側的常規顏色中,從破曉藍-洋紅這段偏冷色系幾個顏色中,差值趨勢變化最平緩的是「極客藍」這顏色,這說明該顏色在深淺背景下的表現較為穩定,起伏不大,當基于一個統一的透明度規則前提下,會讓它在暗色下的對比度減弱,所以反而會導致差值趨勢變大,所以我們會發現差值趨勢變化較小的顏色轉移到了「破曉藍」、「洋紅」中,也是一個正常現象。
最后可以看到顏色在調整過后實際應用的效果
在官網中點擊「切換」即可預覽:
如果上述 12 個色板不滿足你的業務需求,你也可以在官網上自己選擇顏色,我們會根據規則幫你生成一個暗色色板。
另外,如果在實際應用過程中,你選了色相在 225~325 間的深冷色系作為主色或強調色使用,建議適當提高透明度的值,避免在暗色界面上引起閱讀障礙。
暗黑模式中,文字的使用與淺色模式基本一致,依舊從 85%-65%-45%,唯一不同的地方在 disable 的狀態,其透明度值提升為 30%,避免顏色過淡真的「不可見」。另外,對于 #FFFFFF 的純白色文字,盡量避免大面積使用,尤其對于表格、列表這類偏閱讀瀏覽的場景,如有需要,做小范圍強調即可。
暗黑模式中的陰影規則與淺色模式基本保持一致,但由于本身的界面背景較深,在陰影色值上也有所加深,幫助層次更好的體現,整體將色值擴大到原先的 4 倍,但在陰影的位移、擴展上均保持不變。
分割線在暗黑模式中建議根據界面中常用的背景色,通過透明度換算成實色使用,Ant Design 中分割線主要有 #434343 和 #303030 兩種顏色,分別對應淺色模式下的 #D9D9D 和 #F0F0F0,這樣做的目的主要是為了避免帶來額外的交錯疊加,尤其對于表格類以及很多帶有 border 屬性的組件,實色會更為穩妥便于記憶。
適應性方面,Ant Design 的暗黑模式可以與海兔及可視化資產進行無縫銜接,使用時可以任意組合拖拽,你可以下載 Kitchen 插件,獲取海量資產。
暗黑模式最近越來越受到人們的關注,與某一特定產品的暗黑設計不同,Ant Design 的暗黑模式更多以設計體系的角度考慮企業級這個大場景下的內容,在易用性、擴展度、復用度等方面還有許多需要完善和繼續研究探索的地方,我們會在未來的迭代中逐步進行,先完成再完善。希望上述內容能對大家在暗黑模式的設計上有所幫助。
文章來源:優設 作者:AlibabaDesign
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
JavaScript不具有 sleep() 函數,該函數會導致代碼在恢復執行之前等待指定的時間段。如果需要JavaScript等待,該怎么做呢?
假設您想將三則消息記錄到Javascript控制臺,每條消息之間要延遲一秒鐘。JavaScript中沒有 sleep() 方法,所以你可以嘗試使用下一個最好的方法 setTimeout()。
不幸的是,setTimeout() 不能像你期望的那樣正常工作,這取決于你如何使用它。你可能已經在JavaScript循環中的某個點上試過了,看到 setTimeout() 似乎根本不起作用。
問題的產生是由于將 setTimeout() 誤解為 sleep() 函數,而實際上它是按照自己的一套規則工作的。
在本文中,我將解釋如何使用 setTimeout(),包括如何使用它來制作一個睡眠函數,使JavaScript暫停執行并在連續的代碼行之間等待。
瀏覽一下 setTimeout() 的文檔,它似乎需要一個 "延遲 "參數,以毫秒為單位。
回到原始問題,您嘗試調用 setTimeout(1000) 在兩次調用 console.log() 函數之間等待1秒。
不幸的是 setTimeout() 不能這樣工作:
setTimeout(1000)
console.log(1)
setTimeout(1000)
console.log(2)
setTimeout(1000)
console.log(3)
for (let i = 0; i <= 3; i++) {
setTimeout(1000)
console.log(`#${i}`)
}
這段代碼的結果完全沒有延遲,就像 setTimeout() 不存在一樣。
回顧文檔,你會發現問題在于實際上第一個參數應該是函數調用,而不是延遲。畢竟,setTimeout() 實際上不是 sleep() 方法。
你重寫代碼以將回調函數作為第一個參數并將必需的延遲作為第二個參數:
setTimeout(() => console.log(1), 1000)
setTimeout(() => console.log(2), 1000)
setTimeout(() => console.log(3), 1000)
for (let i = 0; i <= 3; i++) {
setTimeout(() => console.log(`#${i}`), 1000)
}
這樣一來,三個console.log的日志信息在經過1000ms(1秒)的單次延時后,會一起顯示,而不是每次重復調用之間延時1秒的理想效果。
在討論如何解決此問題之前,讓我們更詳細地研究一下 setTimeout() 函數。
檢查setTimeout ()
你可能已經注意到上面第二個代碼片段中使用了箭頭函數。這些是必需的,因為你需要將匿名回調函數傳遞給 setTimeout(),該函數將在超時后運行要執行的代碼。
在匿名函數中,你可以指定在超時時間后執行的任意代碼:
// 使用箭頭語法的匿名回調函數。
setTimeout(() => console.log("你好!"), 1000)
// 這等同于使用function關鍵字
setTimeout(function() { console.log("你好!") }, 1000)
理論上,你可以只傳遞函數作為第一個參數,回調函數的參數作為剩余的參數,但對我來說,這似乎從來沒有正確的工作:
// 應該能用,但不能用
setTimeout(console.log, 1000, "你好")
人們使用字符串解決此問題,但是不建議這樣做。從字符串執行JavaScript具有安全隱患,因為任何不當行為者都可以運行作為字符串注入的任意代碼。
// 應該沒用,但確實有用
setTimeout(`console.log("你好")`, 1000)
那么,為什么在我們的第一組代碼示例中 setTimeout() 失???好像我們在正確使用它,每次都重復了1000ms的延遲。
原因是 setTimeout() 作為同步代碼執行,并且對 setTimeout() 的多次調用均同時運行。每次調用 setTimeout() 都會創建異步代碼,該代碼將在給定延遲后稍后執行。由于代碼段中的每個延遲都是相同的(1000毫秒),因此所有排隊的代碼將在1秒鐘的單個延遲后同時運行。
如前所述,setTimeout() 實際上不是 sleep() 函數,取而代之的是,它只是將異步代碼排入隊列以供以后執行。幸運的是,可以使用 setTimeout() 在JavaScript中創建自己的 sleep() 函數。
如何編寫sleep函數
通過Promises,async 和 await 的功能,您可以編寫一個 sleep() 函數,該函數將按預期運行。
但是,你只能從 async 函數中調用此自定義 sleep() 函數,并且需要將其與 await 關鍵字一起使用。
這段代碼演示了如何編寫一個 sleep() 函數:
const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay))
const repeatedGreetings = async () => {
await sleep(1000)
console.log(1)
await sleep(1000)
console.log(2)
await sleep(1000)
console.log(3)
}
repeatedGreetings()
此JavaScript sleep() 函數的功能與您預期的完全一樣,因為 await 導致代碼的同步執行暫停,直到Promise被解決為止。
一個簡單的選擇
另外,你可以在第一次調用 setTimeout() 時指定增加的超時時間。
以下代碼等效于上一個示例:
setTimeout(() => console.log(1), 1000)
setTimeout(() => console.log(2), 2000)
setTimeout(() => console.log(3), 3000)
使用增加超時是可行的,因為代碼是同時執行的,所以指定的回調函數將在同步代碼執行的1、2和3秒后執行。
它會循環運行嗎?
如你所料,以上兩種暫停JavaScript執行的選項都可以在循環中正常工作。讓我們看兩個簡單的例子。
這是使用自定義 sleep() 函數的代碼段:
const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay))
async function repeatGreetingsLoop() {
for (let i = 0; i <= 5; i++) {
await sleep(1000)
console.log(`Hello #${i}`)
}
}
repeatGreetingsLoop()
這是一個簡單的使用增加超時的代碼片段:
for (let i = 0; i <= 5; i++) {
setTimeout(() => console.log(`Hello #${i}`), 1000 * i)
}
我更喜歡后一種語法,特別是在循環中使用。
總結
JavaScript可能沒有 sleep() 或 wait() 函數,但是使用內置的 setTimeout() 函數很容易創建一個JavaScript,只要你謹慎使用它即可。
就其本身而言,setTimeout() 不能用作 sleep() 函數,但是你可以使用 async 和 await 創建自定義JavaScript sleep() 函數。
采用不同的方法,可以將交錯的(增加的)超時傳遞給 setTimeout() 來模擬 sleep() 函數。之所以可行,是因為所有對setTimeout() 的調用都是同步執行的,就像JavaScript通常一樣。
希望這可以幫助你在代碼中引入一些延遲——僅使用原始JavaScript,而無需外部庫或框架。
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
1. 隨機排列
在開發者,有時候我們需要對數組的順序進行重新的洗牌。 在 JS 中并沒有提供數組隨機排序的方法,這里提供一個隨機排序的方法:
function shuffle(arr) {
var i, j, temp;
for (i = arr.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
return arr;
}
2. 唯一值
在開發者,我們經常需要過濾重復的值,這里提供幾種方式來過濾數組的重復值。
使用 Set 對象
使用 Set() 函數,此函數可與單個值數組一起使用。對于數組中嵌套的對象值而言,不是一個好的選擇。
const numArray = [1,2,3,4,2,3,4,5,1,1,2,3,3,4,5,6,7,8,2,4,6];
// 使用 Array.from 方法
Array.from(new Set(numArray));
// 使用展開方式
[...new Set(numArray)]
使用 Array.filter
使用 filter 方法,我們可以對元素是對象的進行過濾。
const data = [
{id: 1, name: 'Lemon'},
{id: 2, name: 'Mint'},
{id: 3, name: 'Mango'},
{id: 4, name: 'Apple'},
{id: 5, name: 'Lemon'},
{id: 6, name: 'Mint'},
{id: 7, name: 'Mango'},
{id: 8, name: 'Apple'},
]
function findUnique(data) {
return data.filter((value, index, array) => {
if (array.findIndex(item => item.name === value.name) === index) {
return value;
}
})
}
3. 使用 loadsh 的 lodash 方法
import {uniqBy} from 'lodash'
const data = [
{id: 1, name: 'Lemon'},
{id: 2, name: 'Mint'},
{id: 3, name: 'Mango'},
{id: 4, name: 'Apple'},
{id: 5, name: 'Lemon'},
{id: 6, name: 'Mint'},
{id: 7, name: 'Mango'},
{id: 8, name: 'Apple'},
]
function findUnique(data) {
return uniqBy(data, e => {
return e.name
})
}
3. 按屬性對 對象數組 進行排序
我們知道 JS 數組中的 sort 方法是按字典順序進行排序的,所以對于字符串類, 該方法是可以很好的正常工作,但對于數據元素是對象類型,就不太好使了,這里我們需要自定義一個排序方法。
在比較函數中,我們將根據以下條件返回值:
小于0:A 在 B 之前
大于0 :B 在 A 之前
等于0 :A 和 B 彼此保持不變
const data = [
{id: 1, name: 'Lemon', type: 'fruit'},
{id: 2, name: 'Mint', type: 'vegetable'},
{id: 3, name: 'Mango', type: 'grain'},
{id: 4, name: 'Apple', type: 'fruit'},
{id: 5, name: 'Lemon', type: 'vegetable'},
{id: 6, name: 'Mint', type: 'fruit'},
{id: 7, name: 'Mango', type: 'fruit'},
{id: 8, name: 'Apple', type: 'grain'},
]
function compare(a, b) {
// Use toLowerCase() to ignore character casing
const typeA = a.type.toLowerCase();
const typeB = b.type.toLowerCase();
let comparison = 0;
if (typeA > typeB) {
comparison = 1;
} else if (typeA < typeB) {
comparison = -1;
}
return comparison;
}
data.sort(compare)
4. 把數組轉成以指定符號分隔的字符串
JS 中有個方法可以做到這一點,就是使用數組中的 .join() 方法,我們可以傳入指定的符號來做數組進行分隔。
const data = ['Mango', 'Apple', 'Banana', 'Peach']
data.join(',');
// return "Mango,Apple,Banana,Peach"
5. 從數組中選擇一個元素
對于此任務,我們有多種方式,一種是使用 forEach 組合 if-else 的方式 ,另一種可以使用filter 方法,但是使用forEach 和filter的缺點是:
在forEach中,我們要額外的遍歷其它不需要元素,并且還要使用 if 語句來提取所需的值。
在filter 方法中,我們有一個簡單的比較操作,但是它將返回的是一個數組,而是我們想要是根據給定條件從數組中獲得單個對象。
為了解決這個問題,我們可以使用 find函數從數組中找到確切的元素并返回該對象,這里我們不需要使用if-else語句來檢查元素是否滿足條件。
const data = [
{id: 1, name: 'Lemon'},
{id: 2, name: 'Mint'},
{id: 3, name: 'Mango'},
{id: 4, name: 'Apple'}
]
const value = data.find(item => item.name === 'Apple')
// value = {id: 4, name: 'Apple'}
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
新擬物化——Neumorphism ,這么說可能不容易理解,但如果說「新擬物風格」,想必 UI 界的設計師們就知道這股「風頭」,在2020年刮的多么兇猛了。
烏克蘭設計師亞歷山大·普盧托 (Alexander Plyuto) 在 Dribble 平臺發布了一張 UI 作品《Skeuomorph Mobile Banking》,由于該作品使用了擬物化的設計風格,令人耳目一新,導致了作品的熱度持續飆升,并登上了平臺 Popular 榜首。Dribble 的評論區直接炸開了鍋,大家紛紛討論。
△ 普盧托的《Skeuomorph Mobile Banking》,獲得了3000多次喜歡
隨后一位評論者杰森·凱利(Jason Kelley)在評論中將 New Skeuomorphism 「新擬物化」組合得到的 Neuomorphism 稱為「新擬物」 ,并決定去掉「 o 」,于是新設計詞匯「 Neumorphism 」便產生了。之后大家便用此做 Tag ,為自己的新擬物化設計作品打標簽上傳。
此風格的出現也給一直流行的扁平化設計添加了新的展現形式。今年2月初,三星召開 Galaxy Unpacked 活動,為宣傳新設備而發出的邀請函,便應用了新擬物化。
△ 凸出的部分,用來比喻新機型的賣點
想要了解新擬物的由來,就必須知道擬物的概念。擬物又被稱為擬物化,或是現實主義(Realism),概括的說其主要目標是使用戶界面更有代入感,降低人們使用的學習成本,產生熟悉親和的情感聯系。
A skeuomorph, or skeuomorphism is a design element of a product that imitates design elements functionally necessary in the original product design, but which have become ornamental in the new design. Skeuomorphs may be deliberately employed to make the new look comfortablyold and familiar.
via:en.wikipedia.org/wiki/Skeuomorph維基百科上關于擬物化的釋義
Apple 蘋果公司最早提出了擬物化的設計概念,通過模擬現實物體的紋理、材質來進行界面設計,當時的 UI 設計師們都為擬物化設計「癡狂」。蘋果創始人喬布斯也非常推崇擬物化,他認為:「通過擬物化,用這種更加自然的認知體驗方式,可以減少用戶對電腦操作產生的恐懼感」。不妨來回憶下曾經擬物化的 IOS 界面:
△ IOS 5系統中的相機展開狀態(擬物化的鏡頭)
△ 擬物化的精美 ICON
△ IOS 6系統中,被精細刻畫的錄音機(底部指針也很惟妙惟肖)
而新擬物則是擬物的變體,在擬物的基礎上改變了圖形的樣式,讓設計元素看起來更有真實感,不再是精細的模擬,更像是從界面中「生長」出來。設計師 Michal Malewicz 以卡片的形式,將新擬物和質感設計(Material Design)對比,闡述了二者在實現時的差別。
新擬物卡片給人呈現的是一種無縫隙的「閉合」感,由界面中凸起;而質感設計卡片,則是漂浮狀,陰影向四周發散,沒有邊界限制;二者的光影效果也非常明顯,新擬物偏柔和,質感設計則相反,非常凸顯物體本體。
Michal Malewicz 還標注了新擬物卡片的背景、陰影和高光的色值,整體色調比較接近。
擬物化風格的結構由背景色+高光色+陰影組成,掌握了基本規律,就可以通過改變按鈕、卡片的參數進行調整變換。
△ 形狀、陰影參數的不同,實際效果也有區別
新擬物也經常被拿來與扁平化比較,因為擬物和扁平化是兩個相對的概念。其實在蘋果創造的設計系統的早期界面其實是非常擬物風的,但系統從 IOS 7開始,才轉向扁平的設計風格。
隨著 AR、VR 技術的進步,其實對于真實物理環境,或者說對顯示效率的提升之后,我們對接近物理環境的設計更熱衷了。比較有代表性的就是 Google 推出的 Material Design System,它基于人們去模擬真實的物理世界的樣子,進而在數字世界里展現我們對于真實世界的一個反饋后,這樣的設計流程和邏輯,也讓我們的設計更真實,更具有感染力。當然也不止 Google 一家發布了這樣偏擬物化的設計風格。
從美學角度來看,其實新擬物化拋棄了之前很多擬物化里不必要的冗余,比如一些陰影、細節的繁瑣設計,更偏近現在先進科技發展的設計風格。比如 Windows 推出的 Fluent Design System ,正迎合了未來的 AR、VR 技術廣泛普及后的設計環境,希望打造一個先趨的設計系統。
在 Fluent Design System 提到的特點有:Lignt、Depth、Motion、Material、Scale。
1. Lignt
光照,它指的是點擊或 Hover 的動作上面加入光照的效果,或是像柔和的光源灑落在空間中,可以去反應物體本身的反光質感,它和 Material Design 強調的光影的擴散的光影效果是不同的概念。
2. Depth
深度,其實它的概念從 Material Design 開始就已經被強調了,但是 Fluent Design System 希望是用更多的方式去呈現,比如井深的模糊效果,視差滾動的動態效果,物件彼此的大小與位置等等。
3. Motion
動效,其實它想強調的動態效果更接近真實的世界,更強調細膩的變化,比如李安的電影「比利·林恩的中場戰事」,這個電影拍攝的幀數與以往傳統電影不一樣,看起來的感覺會更加的流暢自然,你體驗過之后會很難回去之后那種電影呈現效果了。而 Windows 強調的 Motion 也是一樣的,比起這種單調的動作,它也會去強調每個設計對象彼此之間的動態效果的時間差,看起來會更加的流暢自然。而且與真實空間中前景后景的物理概念一樣,不同的時間差會更容易凸顯出想要凸顯的主題效果,也會更加的聚焦。
4. Material
材質,其實在 Windows 提出的 Fluent Design System 里面,它會出現大量的模糊,透明的背景。也就是模擬毛玻璃的材質感。通常也會代入一些微光源的效果。除了能夠吸睛,吸引你的視覺之外呢,其實在 AR、VR 的界面上面感知空間中的物件是很重要的,所以模糊的背景的利用可以在不影響觀看內容情況下,還能起到背景暗示的作用。其實毛玻璃效果在 Windows 系統中已經被運用到了,但是由于當時的效能以及干擾視線的問題僅僅運用在了一些小區域,而這次 Fluent Design System 的就成為了最強烈的視覺焦點,其實同樣的 iOS 和 Mac iOS 系統里面在最近的更新也大量使用了模糊效果。
6. Scale
縮放,在視覺上眼前的物體大,后面的物體小,所以縮放也是來營造空間感、縱深感,尺度感的這樣一個設計特性。
常應用于圖標、卡片或按鈕元素設計上,背景板多為干凈的純色;界面平滑,沒有明顯的顆粒感;
△ HYPE4《 Neumorphic Bank Redesign in Dark and Light mode 》
△ Filip Legierski 《 Banking App 》
按鈕的外邊框均設置了陰影、漸變效果,突出立體感;
△ Samson Vowles《 Neumorphic dark ui kit components 》
在視覺處理上,凸出的按鈕為可點擊的狀態,凹進去則代表已選中。
△ Emy Lascan《 Freebie Neumorphic UX UI Elements 》
層次結構弱
Whale Lab 觀察發現,新擬物弱化顏色區分而強調近遠景陰影布局,所以整體色彩都相近,除了在個別的位置加入其它顏色點綴,用戶識別起來也會迷茫;而卡片、按鈕都使用了陰影,高光效果,層次結構不明確,也很難帶來流暢的體驗;
△ 新擬物風格,Filip Legierski《 Neumorphism UI Elements 》
對比度和視覺限制
明顯的對比是界面設計的重要原則。由于新擬物風格具有各種陰影色調和角度,可單擊的內容與不可單擊的內容區域在哪里不是很好區分。根據產品的功能和要求,每個應用神經同構的產品都可以具有自己的UI階段規則;但是由于陰影,角度和浮動水平的不同,由于缺乏一致性,迷失方向的可操作項,「神經變形」會給用戶帶來混亂,最終為殘疾用戶造成使用障礙。
如同下面這個例子,按鈕狀態已點擊和未點擊的一個效果,由于受壓狀態的反差太小,則看起來的效果也沒有什么不同。
增加開發難度
更為嚴重的是,不少設計者在使用 Neumorphism 進行界面開發過程中,也遭遇到了不少局限。要實現這個風格,主要有兩個方式:
卡片、按鈕切圖,每個狀態(Normal、Hover、Pressed)都要裁切,導致資源庫圖片量過載;
代碼實現,這個風格的實現效果是對元素增加兩個不同方向的投影,但需要開發對每個元素添加投影,樣式代碼增多,工作量浩大。
網站neumorphism.io,可以快速生成 Neumorphism UI 。設置按鈕的參數值,就能看到多樣的新擬物化效果,非常神奇!
新技術、事物、趨勢的出現,起初都會給人們帶來焦慮甚至是恐慌。不管是擬物還是扁平,Whale Lab 覺得若是絕對化的去推崇某一種,都是錯誤的,盡管蘋果放棄了擬物進入扁平化,也不一定代表扁平就是最好,畢竟二者始終相輔相成。不敢否認,新擬物風格在今后是否變得「真香」,但對于設計師來說,從用戶體驗、產品出發的優秀設計,都值得被認可與尊敬。
文章來源:優設 作者:UX辭典
藍藍設計的小編 http://www.syprn.cn