It is ultra experience
ISUX Design Trend Report
——————————
身為用戶體驗設計師,無時無刻不被世界上的新事物沖刷著認知——互聯網紅利下降帶來變化莫測的商業動向、循著摩爾定律野蠻生長日新月異的新技術、各類亞文化群體催生出多元復雜的圈層文化、腦洞口味越來越獨特的年輕人,甚至眼下席卷全球的黑天鵝事件......
任何一個新事物的悄悄冒頭,都有可能在未知的將來影響著用戶體驗設計師。我們能做的是,在起初感受到微微震幅時,便沿著震感逐步尋找源頭,并思考未來的發展走向。趕在變化降臨前先擁抱變化。
本文通過研究近一兩年科技、社會文化以及自身用戶體驗領域的變化,從用戶體驗領域關鍵的用戶、媒介(設備與應用)、交互行為、信息與場景的五個角度出發,探索用戶體驗設計未來的趨勢,希望能帶來啟發。
隨著人工時代到來,過去機械的單向交互方式逐漸被打破,機器漸漸演化成了會主動“觀察”真實場景,“感受”用戶情感,預判用戶意圖并自動完成任務的貼心小棉襖。機器如何為人們提供更智能便捷的服務,未來還有非常大的想象空間。
隨著人工時代到來,過去機械的單向交互方式逐漸被打破,機器漸漸演化成了會主動“觀察”真實場景,“感受”用戶情感,預判用戶意圖并自動完成任務的貼心小棉襖。機器如何為人們提供更智能便捷的服務,未來還有非常大的想象空間。
隨著AI技術的發展,智能設備可以越來越無縫地將數字世界和物理世界嫁接起來,主動感知用戶所處情境并智能提供相應服務。
在2019的 Google I/O 大會上,Google Lens 展示的AR點菜功能可以智能識別用戶掃描的菜單并將美食網站上的相關推薦直接呈現在屏幕上。
當用 Google Lens 識別到小票信息時,可快速提取小票上的金額,且可自動彈起計算器快速幫助用戶計算人均消費,節省人工計算的時間成本。
隨著信息入口從數字空間延伸到周圍的物理空間中,未來萬物皆可為用戶體驗的媒介,設計師未來在設計的時候需要注意:
尋找適合的打通真實世界的切入點:在陌生語言、信息復雜或者難以處理等苛刻的環境下,充分發揮智能設備對信息智能讀取、批量識別與翻譯等強大能力,幫助用戶完成任務;
將用戶旅程的上下游串聯:根據生活常識和經驗預判用戶行為目的,前置推薦服務;
更加系統細心地考量干擾因素:真實場景是動態變化的,需要更全方位考慮光線的強弱、多源的噪音、實體的可視性、人員和事件的打斷等因素。
為了完成一項任務,用戶往往需要借助多個應用來回切換配合,使用起來瑣碎麻煩。如今應用越做越強大也越復雜,過去僅僅解決單一場景的解決方案不再適應于用戶對于完成任務的訴求。
Google Assistant 的新能力 Duplex on the web 可以通過自動跨應用任務處理來簡化用戶旅程。只需要用戶發出語音指令“預定一輛去某地的車”,助手便可自動跨郵件、日歷、付款等應用調取信息、自動根據使用習慣做選擇,并自動填寫信息,而用戶全程需要的只是在關鍵節點輕敲“確認”即可。
2019年隨著 iOS 13 的更新,“快捷指令”推出了“自動化”能力,用戶通過“if...then...”語法便可為自己的App設計一套程序,實現如:“當我回到公司時提醒我打卡”、“每天早上10點給我的女朋友發送一條表白短信”等能力,將不相關的場景動作串聯字一起自動化執行,大大節省人工操作成本。
提升使用效率是用戶體驗設計孜孜不倦努力的方向之一。在利用新技術進一步簡化用戶旅程時,設計師可以充分利用以下因素:
借助語音輸入:比起界面觸控操作,語音交互的直達性可以“穿透”復雜界面,讓設備第一時間明確用戶目標;
基于用戶行為形成習慣記憶:對用戶長期重復的行為做分析處理,構建用戶習慣模型并主動提供服務;
適當考慮專家級用戶:隨著部分用戶的智能設備使用水平越來越高,可以考慮為專家用戶提供自定義操作腳本,滿足其自身的獨特需求。
隨著人臉識別、表情識別、肢體跟蹤等技術的提升,機器逐漸學會感性語言,主動感知用戶內在情感和心理需求。
2019年1月的CES展上起亞亮相的互動式“情感駕駛空間”技術,可通過傳感器讀取用戶的面部表情、心率等反應,調整駕駛空間內的燈光、影片類型、音樂風格等,舒緩艙內乘客心情,由此提供更人性化的出行體驗。
用戶總是會期待更貼心的服務,設計師未來對同理心的情感嗅覺更加敏銳:
利用感性線索定位用戶情緒:需要通過面部表情、特殊時間節點或者識別到的關鍵詞,對用戶情緒進行理解和定位,判斷用戶情感理解用戶內心訴求是自由探索、趣味娛樂、或者靜謐修行并提供符合用戶當下心境的服務。
綜合使用感性元素進行設計:通過使用線條、色彩、聲音和動作等傳達并喚起相對應的情感,提供更加人性化的體驗。
更智能的服務提供方式會讓人們生活擁有更多可能性,但一旦火候把握不得當,可能就會造成對人們生活的野蠻入侵。關于如何讓科技更好造福于人們,早在上個世紀,施樂帕克研究中心提出了寧靜技術(Calm Technology)的愿景,認為影響最深遠的技術應該是隱匿不見的,它們如纖維般融入日常生活,絲絲入扣,直至不可分辨。
隨著科技的發展,設計師對新技術不應是不加克制地應用,而應該潤物細無聲般地提供服務,幫助人們從繁雜喧囂的數字世界中解脫出來,將寶貴的注意力資源投放在讓生活更美好的事物上。
回顧人類和機器的交流語言,從命令行界面、圖形用戶界面到自然用戶界面,人機交互方式越來越貼合人與人之間更自然的交流方式,其背后是心智模型與實現模型的高度擬合的趨勢。
在自然用戶界面中,為滿足新形態智能硬件對新接口的需求,以及人們對更豐富強大的交互方式的自然訴求,越來越多的自然用戶界面被開發出來。語音交互和隔空手勢交互便是近幾年迅速發展并落地的兩種交互方式。
為了讓機器更好地讀懂用戶的身體語言,能夠感知深度信息的攝像頭走進了日常手機。2019年國內外手機廠商的發布大會上,LG 手機 G8 ThinQ 以及華為發布 Mate 30 系列推出的隔空手勢,可實現一些簡單的諸如滑動、切歌、截屏等效果。
除此以外,隔空手勢支持更加細微的手勢,如旋轉、揉搓等,可以更直觀、更靈活的方式操控界面,讓用戶獲得一種像魔術師用意念控制事物運作的快感。
對于隔空手勢操作網上的言論褒貶不一,其中爭議性最大的就是隔空手勢宛如“殺雞用牛刀”,明明可以用更加精準的手勢觸控,為什么還要用看似很酷炫其實精準度更低的隔空手勢操作?
隔空手勢并不是要替代觸控手勢成為主流的人機交互方式,更多是對情境式障礙場景的補充。在某些場景下,用戶使用設備的條件可能是充滿干擾的。想想看當你邊看手機食譜邊炒菜的時候、邊煲劇邊剝小龍蝦的時候、疫情期間出門佩戴橡膠手套無法正常觸控手機屏幕時.....隔空手勢是不是特別好用?
每個人在特殊的場景下都有可能面臨感官障礙,未來的設計也應該更多地考慮情境式障礙的場景,讓用戶無論身處何時何地依舊能一如既往無障礙地使用設備。
語音交互作為更趨近于人與人之間最自然的交流方式,近些年有許多發展的突破點。
在發展主線上,語音交互趨向更自然、更人性化、更個性化。過去反人類的一些溝通方式慢慢被“調教”。此外,多人會話場景下的技術方案日漸增多。
2019的 Google I/O 大會展示了一個視頻片段,視頻中的兩位嘉賓相繼吐槽,經常出現針鋒相對難以聽清的時候,這時用戶可以調節音源音量選擇性增強自己關注的人物聲音,讓另一個人“靜音”。
滑動選擇音源
此外,語音交互除了在智能音箱領域廣泛應用以外,也逐漸應用在廣告等更多的傳播媒介中,刷新人們日常使用體驗。2020年2月索尼提交了一項廣告播放新專利。當用戶在觀看電視節目時,如果出現廣告,只要站起來大喊廣告中對應品牌的名字,便可直接跳過這個廣告。
設計師在語音交互場景下,需要留意以下幾個比較容易被忽視的因素:
用戶語音交互習慣培養:如今還處于培養用戶語音交互使用習慣階段,設計師需要更多地考慮應用的語音交互規則如何才能更趨近于人們日常的溝通習慣,并進一步為人們的社會習俗所接納。
真實場景下的多人音源:在現實情境中, 在多人對話場景下將面臨音源不清、穿插停頓、噪音過多等影響體驗的情況,由于計算機聽覺分析能力開始從單人音源拓寬到了多人音源,多人對話解決方案上還有很大想象空間。
改變傳統的視聽體驗:在使用場景上,語音交互接口也將逐漸運用到更多的媒介上,更全面地刷新用戶體驗。
人類擁有雙手、眼睛、耳朵和發聲的嘴巴,但是并不總是在每個使用場景下都能自如地使用:在安靜的自習室下聲音收到限制,在駕駛場景下注意力受到限制,在雙手拎著東西場景下雙手受到限制......但目前許多產品設計都建立在用戶能完整使用感官功能這一理想化的基礎上。
未來的發展趨勢傾向于將視、聽、觸、嗅等多通道信息完美整合起來,綜合使用多種輸入通道和輸出通道,根據用戶使用場景用最恰當的方式傳遞服務,滿足用戶多方位的需求。
盡管喬布斯曾斷言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宣傳視頻
餓了么:在餓了么送貨騎手中,約8%受色盲色弱的困擾(全國男性群體中紅綠色盲色弱占比達8%-9%,餓了么騎手男性占比90%),為此餓了么設計團隊在2019年對app的進行了重新設計,包括使用WCAG無障礙色彩對比度,以及無障礙色盤,以及調整字階,使用輔助圖形等設計手段來解決部分騎手在送貨途中使用APP的痛點問題。
餓了么UED:《為騎士創造平等 — 配送 App 的包容性設計》
跨年齡段設計
谷歌助手禮貌功能 ( Google Pretty Please ) :開啟谷歌助手禮貌功能后,如果使用者在下達指令的語句中包括“Please”,谷歌助手會對禮貌的請求表示感謝,以此培養孩子的禮貌言行。
Google Pretty Please功能宣傳
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設計趨勢,一方面是對往年風格的衍變和細化,另一方面,在扁平克制的界面風格盛行后,設計師們向往更自由、更突破的視覺表達。
2019年iOS 13深色模式姍姍來遲,緊接著大廠APP相繼推出此功能。在2020年,深色模式會繼續普及外,也會在可視性和實現成本方面有更多細節打磨和研究。
設計趨勢的發展是螺旋式上升的,在扁平化設計流行之后,對物體的擬真再一次回歸設計圈,新擬態以一種對舊擬物風格的再創新,重新流行起來。
新擬物風格(Neumorphism)緣起于設計師Alexander Plyuto發布在dribbble的一組作品,以投影重新對扁平界面進行了塑造,模仿出類似浮雕的視覺效果,感受耳目一新,引起大量設計師相盡模仿。
新擬態的實用性和可落地性有待商榷,但是作為一種新的風格受到設計師擁躉,也不失為下一波風潮到來前的靈感繆斯。
WWDC2020對mac OS的更新也重新定義了新擬態設計語言,在mac OS新系統Big Sur中,圖標的設計增添了輕微的漸變、投影、高光,以此來營造圖標內元素之間的縱深關系。
在扁平簡潔UI風格盛行之后,豐富的色彩依舊是設計趨勢之一,大面積色塊,碰撞配色,帶來更具沖擊感的視覺體驗。
UI界面逐漸扁平,色塊圖標弱化,為突出頁面重心和內容,iOS 11在界面標題上使用更大的字號,更粗的字重。近年在大標題的風格衍變下,文字在傳達信息外,也開始有了裝飾性作用,采用超大字體,成為頁面排版美化的一部分。
大圓角的風格會繼續延續,相較以往,卡片的處理圓角會更大,隨之帶來的是多的留白處理,結合大字號,帶來更透氣通透的視覺感受。
Mac OS Big Sur的界面相對舊版本采用了更大的圓角;系統圖標的設計統一成圓角矩形。
UI插圖的豐富體現在樣式和內容上,樣式上開始3D化,內容上更注重插圖敘事的表達。
3D插圖
3D圖形往年更多運用在動態影像或運營類設計中,隨著3D的普及運用,UI插圖也會迎來3D化,給用戶帶來更立體,更新鮮的視覺感受。
講求敘事表意
相較于往年追求形式的UI插圖,新趨勢下的插圖更講求功能性,每一副插圖都承載一定的作用——傳達功能信息或透傳品牌情感;同時插圖更講求畫面表意和情節,給用戶敘事性的視覺體驗,增進用戶和產品之間的情感聯系。
插圖組件化
插畫的流行,隨之而來的是成本的水漲船高——一套系列插圖為保持風格統一,往往由唯一設計師繪制,同時為兼容各類場景,設計師往往要繪制多張。
為解決插圖的成本和效率,插圖開始以組件化的方式進行繪制——插圖設計師將插畫進行拆分繪制——不同人物,不同場景,不同物件等,再通過組件化的拼接合成,使用組件的設計師可以根據需求場景自由組合,也避免了風格不統一問題。
設計師Pablo Stanley將日常繪制的插畫制成一套矢量插圖組件庫,將人物分為:半身、全身和坐姿3大類。通過不同表情、發型和服裝可自由搭配出近60萬種組合。
Pablo Stanley人物插畫系統
新趨勢下,動畫一方面回溯復古線描手繪風格,另一方面追求更三維的體驗,同時幀率進一步提升,追求更流暢的視覺感受。
手繪動畫
手繪插圖是往年的熱門,其隨性自然的筆觸,能給用戶帶來親切的感受,在新的趨勢下,動畫的加入賦予手繪插圖一份靈性和趣味。
3D運動
Google Material Design通過卡片投影層級和二維動畫規律,賦予扁平界面Z軸的縱深感。隨著3D的普及流行,新趨勢下的界面,界面的運動從二維走向三維,表現出3D場景下透視感。
高幀率動畫
高幀率影視從線下電影院移步到線上流媒體,手機高幀率屏幕從90Hz到120Hz逐步升級,用戶對畫面流暢的定義一再刷新,UI動畫的幀率升級也會是新的一輪趨勢。
Telegram的表情采用了高幀率動畫,給用戶更流暢的視覺感受。
體驗的持續升級,產品的高速迭代,對UX設計師的設計師效率提出了更高的要求。的設計方式是一個永恒的趨勢。
傳統的文件交接方式效率低下,導致設計師之間信息不對稱,最終影響產品的一致性體驗。近些年在線設計協同工具發展迅速,從UI設計、 設計交付以及組件協同等環節上給設計師提供更加實時的協作體驗,獲得大量UX設計師的簇擁。在2019 uxtool的設計工具調研中,在線設計協同工具佼佼者figma以其協作和性能優勢,大有追趕sketch之勢。
隨著團隊對設計效率要求的提高,設計文檔從本地走向云端協作是不可逆趨勢。不過設計工具的迭代是需要成本的,尤其在大型設計團隊,設計工具需要渡過陣痛期來完成迭代,進而提升設計效率和體驗一致性。
UX的發展,從早期的靜態規范到當下的動態設計系統,是為解決產品迭代增速后帶來的設計效率和產品體驗問題。商業驅動下的產品迭代速度有增無減,設計系統依舊會是未來幾年的設計趨勢之一。
這里說的設計系統不是廣義上的設計系統,而是在互聯網設計的發展中,對組件化設計逐步迭代升華的一套設計協作方法:
“設計系統(Design systems)是一組為了共同目標而服務的內在相互聯系的設計模式和多人協同執行的方法。”(引自《Design systems》,Alla Kholmatova,C7210翻譯)。
設計系統歷程衍變
組件化的發展歷經規范文檔到UI組件,再到設計系統,形態從最初對設計一致性的指導規范,到對產品研發流程的規范,以及產品設計價值觀的輸出,當下的設計系統以集大成者形式影響整個產品的設計形態。
設計系統的結構見下圖:
設計系統的求同存異
設計系統并非一成不變的,他是一個動態進化的系統,會根據團隊性質、產品特性在內容上有所區分——比如大團隊更應該大而全,小團隊更傾向小而精;成熟產品的設計系統更傾向于打造完整閉環的合作流程機制,新產品的設計系統應該以小為始,快速迭代……
隨著產品的垂直化,細分化,設計系統的趨勢會是在趨勢大同之下找到適合產品和團隊自身的形態和節奏。
Material Design是一個包含了指導規范、組件,以及設計開發工具的自適應性設計系統。
它作為平臺型性設計系統,更為大而全的規范了整個生態系統的設計風格,以及提供工具讓研發者能快速產出符合規范的產品。
Google生態龐大繁雜,Material Design更為全面
Ant Design作為一個為to B產品提供解決方案的平臺,更多從設計可用性和完整性考慮設計系統的搭建。
Ant Design通過模塊化解決方案,降低冗余的生產成本,讓設計者專注于更好的用戶體驗
QQ作為一款面向95后的2C社交產品,其設計系統Q語言從風格調性上對設計進行規范,同時給予設計師一定的自由度;也考慮到QQ內兼顧多個產品,以及界面主題樣式,對基礎組件的使用場景和代碼進行了規范,方便設計和開發敏捷開發。
Q語言,給予產品的自由調性之外,也針對主題和基礎組件進行了規范
每個產品和團隊都有自身的特征,設計系統的建設也應該有的放矢,沒有可照搬的標準答案,在大方向下找到適合自身的解決方案才是的可行之道,將效率最大化。
科學有效的優化迭代
組件是設計系統中的重要組成部分,但是以往靜態的、孤立的協作方式使得組件的更新迭代滯后和阻塞。隨著設計系統的發展,設計師組件化思維的普及,組件的更新需要更科學的方式進行管理。
Figma在2019年推出的Design System Analytics功能,組件設計師可以借此查看組件的使用情況,包括引用次數,解組次數等,并可以生成組件使用情況的曲線趨勢圖,以數據的形式,科學地推動組件的優化迭代。
1.選擇分析的時間段;2.組件使用的次數曲線圖;3.團隊使用情況;4.所有組件使用情況
未來的用戶體驗會出現什么新趨勢?人工智能等算法的發展、5G技術普及、新的智能設備形態、新的信息處理技術、新一代用戶的喜好和口味......這些往后或將影響用戶體驗發展的走向。未來用戶對體驗的要求只會越來越高。
用戶體驗設計師需要了解更多的技術動向,但安身立命之本還是讓用戶真正受益:立足于用戶真實使用場景,在理性價值層面上,打造可用、易用、的設計;在感性需求層上賦予情感上的愉悅性,在反思層面賦予意義價值。
文章來源:站酷 作者:百度MEUX
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
用戶隱私安全在產品設計中是很重要的一個環節,本文從用戶體驗角度切入,從匿名模式、減少永久性和減少公開性三個方面展開分析。
我們先看?組來??優的2019年6?的調研數據:70%的美國?認為,與5年前相?個?信息變得更不安全。尤其是?學歷?收?群體。由此可??戶對個?信息數據的隱私擔憂?以往更甚。
?戶隱私安全很重要,涉及的范圍和?度也很多。本次的分析從?戶體驗?度切?,涉及如下三個??:
Incognito Mode匿名模式;
Reducing Permanence減少永久性;
Reducing Publicity減少公開性。
下圖是Google系App(Google AppChromeYouTubeandGoogle Map)匿名模式切換,從交互體驗上來說有如下?個特點:
統?在右上?;
可以便捷切換?匿名模式,反過來也很容易切回登陸狀態;
匿名模式的狀態提示,例如YouTube 在匿名模式下在界?底部有?字提示“您 當前處于匿名模式”。
匿名模式不是最近才流?的功能,最早提供隱匿模式的是蘋果safari瀏覽器,早在 2005年就?持了匿名模式。Chrome瀏覽器在2008年就開始?持此模式。雖然由來已久,為什么到了2020年,匿名模式依然是國外互聯???趨勢呢?
我們看?組數據:
這是來?DuckDuckGo 2019年9?的調研(DuckDuckGo是美國的?款不記錄?戶?為保護?戶隱私的搜索引擎)。樣本來?美國、英國、德國和澳?利亞的成年??戶,共計3,411?的調研得出。各國?戶對使?搜索引擎的個?隱私安全?常在意(是否搜集個?的數據和記錄搜索?為)。
2020年5?DuckDuckGo?均搜索次數為6200萬。對?看2019年11?底?均搜索次數4900萬,2018年10?是2900萬。
最近?年的持續活躍度?幅增?證明了不記錄個?隱私的搜素引擎越來越受到?戶的?睞。
國內,頭條、UC瀏覽器在搜索框輸?狀態也提供了“?痕瀏覽”??。
不僅是搜索引擎領域,保護?戶隱私也成為Facebook最重要的戰略?向之?。Facebook CEO Mark Zuckerberg在2019年 F8開發者?會上喊出“THE FUTURE IS PRIVATE”。2019年3?Mark Zuckerberg發?,主題就是《聚焦于保護隱私的社交?絡》。
我們先看國外社交媒體Stories(?故事)產品形態的流?。
?們總是對于所分享的內容永遠記錄在?上感到擔憂。Stories24?時消失緩解了?們的隱私顧慮,這讓?戶更安?地?然分享。
Stories由Snapchat?創,由 Facebook發揚光?。早在2019年4?,Facebook+Messenger Stories, Instagram Stories?活?戶數就突破5億。 2020年2-3?LinkedIn,Twitter也先后宣布將上線類似功能。
來??優的調研報告:41%的美國?經歷過?絡騷擾,最常?的就是在社交媒體上。23%的?戶最近經歷的?絡騷擾來?評論區的評論內容。27%的?戶經歷?絡騷擾后決定不再發布任何內容。
我們以限定評論互動的公開性為例:
2020年5?Twitter上線了新的評論功能,可以限定誰可以回復帖?的功能,提供了三種選項:誰都可以評論,只有被關注者可以評論,只有被提及者可以評論三種公開度的限定。
Instagram也在測試“評論限制”新功能,批量屏蔽/限制評論。?前已經上線的?個例?:?戶(評論發布者)如果發布的評論含有攻擊性敏感詞,發布前伴有提示,提醒評論含有攻擊性敏感詞是否真的要發布。
注重隱私提供僅好友可?/僅??可?/僅作者可?/等多重維度的隱私設定有助于?戶更安?地參與互動。
另外?個例?是付費頻道會員:付費頻道會員-限定頻道的公開性讓內容創作者減輕隱私顧慮不僅能獲得?告收?,也能得到來?會員、會費的收?,形成“忠實粉絲”社區,有助于內容?態的社區化建設。
我們主要看YouTube的頻道會員案例:
YouTube有兩種會員模式。?種是YouTube整個平臺的付費會員,去?告,看原創美劇影視,消費?樂,可下載內容的模式。第?種模式是Youtuber個?頻道付費會員,吸引忠實粉絲加?。我想說的就是第?種。
為什么?V?紅有意愿開通頻道會員?
除了獲得忠實粉絲收?變現的商業價值以及付費頻道會員可以為忠實粉絲提供各種專屬功能,背后也和?紅?V對個?隱私顧慮有關。
?紅?V在完全公開的社交?絡上需要始終保持?夠克制謹慎,避免引起爭議。但在忠實粉絲付費頻道專屬會員群中,?紅?V會減輕隱私顧慮,更加回歸?我。
?如在頻道會員中發布更多與個??活相關的內容,表達更多不便在完全公開的社交?絡中的想法和感受等,因為忠實粉絲通常更具包容度,更不容易引起爭議。
YouTube頻道會員費?可以從三種會費(按?)區間選擇,?持多選:
低階 Low Levels $0.99~3.99;
中階 Medium Levels $4.99~14.99;
?階 High Levels $19.99~49.99;
頻道會員功能在2018年開始測試,?向粉絲數過10萬的YouTuber開放。
以上綜述,我們可以說:
1.匿名模式:
雖然匿名模式由來已久,但仍然是當前的??基本?戶體驗設計趨勢,尤其是匿名模式的切換便捷性?常重要。
2.減少永久性:
Stories?故事24?時消失緩解了?們的隱私顧慮,這讓?戶更安?地?然分享,已經成為國外社交媒體平臺的必備功能,Facebook, Instagram平臺的最主要、最具影響?的功能之?。
3.減少公開性:
?戶總是對在社交媒體平臺發表評論有所顧忌,限定評論的公開性能夠有助于促進?戶發帖表達,其他?戶也可以更安?地參與互動。
付費頻道會員可以限定頻道的公開性,讓內容創作者減輕隱私顧慮不僅能獲得?告收?,也能得到來?會員會費的收?,形成“忠實粉絲”社區,有助于內容?態的社區化建設。
從UE?度,我們可以為頻道會員提供專屬身份設計例如專屬徽章,專屬表情等。
THE FUTURE IS PRIVATE, 注重?戶隱私的體驗設計越來越重要!
文章來源:站酷 作者:百度MEUX
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
凡是要知其然知其所以然
文件上傳相信很多朋友都有遇到過,那或許你也遇到過當上傳大文件時,上傳時間較長,且經常失敗的困擾,并且失敗后,又得重新上傳很是煩人。那我們先了解下失敗的原因吧!
據我了解大概有以下原因:
服務器配置:例如在PHP中默認的文件上傳大小為8M【post_max_size = 8m】,若你在一個請求體中放入8M以上的內容時,便會出現異常
請求超時:當你設置了接口的超時時間為10s,那么上傳大文件時,一個接口響應時間超過10s,那么便會被Faild掉。
網絡波動:這個就屬于不可控因素,也是較常見的問題。
基于以上原因,聰明的人們就想到了,將文件拆分多個小文件,依次上傳,不就解決以上1,2問題嘛,這便是分片上傳。 網絡波動這個實在不可控,也許一陣大風刮來,就斷網了呢。那這樣好了,既然斷網無法控制,那我可以控制只上傳已經上傳的文件內容,不就好了,這樣大大加快了重新上傳的速度。所以便有了“斷點續傳”一說。此時,人群中有人插了一嘴,有些文件我已經上傳一遍了,為啥還要在上傳,能不能不浪費我流量和時間。喔...這個嘛,簡單,每次上傳時判斷下是否存在這個文件,若存在就不重新上傳便可,于是又有了“秒傳”一說。從此這"三兄弟" 便自行CP,統治了整個文件界?!?
注意文中的代碼并非實際代碼,請移步至github查看代碼
https://github.com/pseudo-god...
分片上傳
HTML
原生INPUT樣式較丑,這里通過樣式疊加的方式,放一個Button.
<div class="btns">
<el-button-group>
<el-button :disabled="changeDisabled">
<i class="el-icon-upload2 el-icon--left" size="mini"></i>選擇文件
<input
v-if="!changeDisabled"
type="file"
:multiple="multiple"
class="select-file-input"
:accept="accept"
@change="handleFileChange"
/>
</el-button>
<el-button :disabled="uploadDisabled" @click="handleUpload()"><i class="el-icon-upload el-icon--left" size="mini"></i>上傳</el-button>
<el-button :disabled="pauseDisabled" @click="handlePause"><i class="el-icon-video-pause el-icon--left" size="mini"></i>暫停</el-button>
<el-button :disabled="resumeDisabled" @click="handleResume"><i class="el-icon-video-play el-icon--left" size="mini"></i>恢復</el-button>
<el-button :disabled="clearDisabled" @click="clearFiles"><i class="el-icon-video-play el-icon--left" size="mini"></i>清空</el-button>
</el-button-group>
<slot
//data 數據
var chunkSize = 10 * 1024 * 1024; // 切片大小
var fileIndex = 0; // 當前正在被遍歷的文件下標
data: () => ({
container: {
files: null
},
tempFilesArr: [], // 存儲files信息
cancels: [], // 存儲要取消的請求
tempThreads: 3,
// 默認狀態
status: Status.wait
}),
一個稍微好看的UI就出來了。
選擇文件
選擇文件過程中,需要對外暴露出幾個鉤子,熟悉elementUi的同學應該很眼熟,這幾個鉤子基本與其一致。onExceed:文件超出個數限制時的鉤子、beforeUpload:文件上傳之前
fileIndex 這個很重要,因為是多文件上傳,所以定位當前正在被上傳的文件就很重要,基本都靠它
handleFileChange(e) {
const files = e.target.files;
if (!files) return;
Object.assign(this.$data, this.$options.data()); // 重置data所有數據
fileIndex = 0; // 重置文件下標
this.container.files = files;
// 判斷文件選擇的個數
if (this.limit && this.container.files.length > this.limit) {
this.onExceed && this.onExceed(files);
return;
}
// 因filelist不可編輯,故拷貝filelist 對象
var index = 0; // 所選文件的下標,主要用于剔除文件后,原文件list與臨時文件list不對應的情況
for (const key in this.container.files) {
if (this.container.files.hasOwnProperty(key)) {
const file = this.container.files[key];
if (this.beforeUpload) {
const before = this.beforeUpload(file);
if (before) {
this.pushTempFile(file, index);
}
}
if (!this.beforeUpload) {
this.pushTempFile(file, index);
}
index++;
}
}
},
// 存入 tempFilesArr,為了上面的鉤子,所以將代碼做了拆分
pushTempFile(file, index) {
// 額外的初始值
const obj = {
status: fileStatus.wait,
chunkList: [],
uploadProgress: 0,
hashProgress: 0,
index
};
for (const k in file) {
obj[k] = file[k];
}
console.log('pushTempFile -> obj', obj);
this.tempFilesArr.push(obj);
}
分片上傳
創建切片,循環分解文件即可
createFileChunk(file, size = chunkSize) {
const fileChunkList = [];
var count = 0;
while (count < file.size) {
fileChunkList.push({
file: file.slice(count, count + size)
});
count += size;
}
return fileChunkList;
}
循環創建切片,既然咱們做的是多文件,所以這里就有循環去處理,依次創建文件切片,及切片的上傳。
async handleUpload(resume) {
if (!this.container.files) return;
this.status = Status.uploading;
const filesArr = this.container.files;
var tempFilesArr = this.tempFilesArr;
for (let i = 0; i < tempFilesArr.length; i++) {
fileIndex = i;
//創建切片
const fileChunkList = this.createFileChunk(
filesArr[tempFilesArr[i].index]
);
tempFilesArr[i].fileHash ='xxxx'; // 先不用看這個,后面會講,占個位置
tempFilesArr[i].chunkList = fileChunkList.map(({ file }, index) => ({
fileHash: tempFilesArr[i].hash,
fileName: tempFilesArr[i].name,
index,
hash: tempFilesArr[i].hash + '-' + index,
chunk: file,
size: file.size,
uploaded: false,
progress: 0, // 每個塊的上傳進度
status: 'wait' // 上傳狀態,用作進度狀態顯示
}));
//上傳切片
await this.uploadChunks(this.tempFilesArr[i]);
}
}
上傳切片,這個里需要考慮的問題較多,也算是核心吧,uploadChunks方法只負責構造傳遞給后端的數據,核心上傳功能放到sendRequest方法中
async uploadChunks(data) {
var chunkData = data.chunkList;
const requestDataList = chunkData
.map(({ fileHash, chunk, fileName, index }) => {
const formData = new FormData();
formData.append('md5', fileHash);
formData.append('file', chunk);
formData.append('fileName', index); // 文件名使用切片的下標
return { formData, index, fileName };
});
try {
await this.sendRequest(requestDataList, chunkData);
} catch (error) {
// 上傳有被reject的
this.$message.error('親 上傳失敗了,考慮重試下呦' + error);
return;
}
// 合并切片
const isUpload = chunkData.some(item => item.uploaded === false);
console.log('created -> isUpload', isUpload);
if (isUpload) {
alert('存在失敗的切片');
} else {
// 執行合并
await this.mergeRequest(data);
}
}
sendReques。上傳這是最重要的地方,也是容易失敗的地方,假設有10個分片,那我們若是直接發10個請求的話,很容易達到瀏覽器的瓶頸,所以需要對請求進行并發處理。
并發處理:這里我使用for循環控制并發的初始并發數,然后在 handler 函數里調用自己,這樣就控制了并發。在handler中,通過數組API.shift模擬隊列的效果,來上傳切片。
重試: retryArr 數組存儲每個切片文件請求的重試次數,做累加。比如[1,0,2],就是第0個文件切片報錯1次,第2個報錯2次。為保證能與文件做對應,const index = formInfo.index; 我們直接從數據中拿之前定義好的index。 若失敗后,將失敗的請求重新加入隊列即可。
關于并發及重試我寫了一個小Demo,若不理解可以自己在研究下,文件地址:https://github.com/pseudo-god... , 重試代碼好像被我弄丟了,大家要是有需求,我再補吧!
// 并發處理
sendRequest(forms, chunkData) {
var finished = 0;
const total = forms.length;
const that = this;
const retryArr = []; // 數組存儲每個文件hash請求的重試次數,做累加 比如[1,0,2],就是第0個文件切片報錯1次,第2個報錯2次
return new Promise((resolve, reject) => {
const handler = () => {
if (forms.length) {
// 出棧
const formInfo = forms.shift();
const formData = formInfo.formData;
const index = formInfo.index;
instance.post('fileChunk', formData, {
onUploadProgress: that.createProgresshandler(chunkData[index]),
cancelToken: new CancelToken(c => this.cancels.push(c)),
timeout: 0
}).then(res => {
console.log('handler -> res', res);
// 更改狀態
chunkData[index].uploaded = true;
chunkData[index].status = 'success';
finished++;
handler();
})
.catch(e => {
// 若暫停,則禁止重試
if (this.status === Status.pause) return;
if (typeof retryArr[index] !== 'number') {
retryArr[index] = 0;
}
// 更新狀態
chunkData[index].status = 'warning';
// 累加錯誤次數
retryArr[index]++;
// 重試3次
if (retryArr[index] >= this.chunkRetry) {
return reject('重試失敗', retryArr);
}
this.tempThreads++; // 釋放當前占用的通道
// 將失敗的重新加入隊列
forms.push(formInfo);
handler();
});
}
if (finished >= total) {
resolve('done');
}
};
// 控制并發
for (let i = 0; i < this.tempThreads; i++) {
handler();
}
});
}
切片的上傳進度,通過axios的onUploadProgress事件,結合createProgresshandler方法進行維護
// 切片上傳進度
createProgresshandler(item) {
return p => {
item.progress = parseInt(String((p.loaded / p.total) * 100));
this.fileProgress();
};
}
Hash計算
其實就是算一個文件的MD5值,MD5在整個項目中用到的地方也就幾點。
秒傳,需要通過MD5值判斷文件是否已存在。
續傳:需要用到MD5作為key值,當唯一值使用。
本項目主要使用worker處理,性能及速度都會有很大提升.
由于是多文件,所以HASH的計算進度也要體現在每個文件上,所以這里使用全局變量fileIndex來定位當前正在被上傳的文件
執行計算hash
正在上傳文件
// 生成文件 hash(web-worker)
calculateHash(fileChunkList) {
return new Promise(resolve => {
this.container.worker = new Worker('./hash.js');
this.container.worker.postMessage({ fileChunkList });
this.container.worker.onmessage = e => {
const { percentage, hash } = e.data;
if (this.tempFilesArr[fileIndex]) {
this.tempFilesArr[fileIndex].hashProgress = Number(
percentage.toFixed(0)
);
}
if (hash) {
resolve(hash);
}
};
});
}
因使用worker,所以我們不能直接使用NPM包方式使用MD5。需要單獨去下載spark-md5.js文件,并引入
//hash.js
self.importScripts("/spark-md5.min.js"); // 導入腳本
// 生成文件 hash
self.onmessage = e => {
const { fileChunkList } = e.data;
const spark = new self.SparkMD5.ArrayBuffer();
let percentage = 0;
let count = 0;
const loadNext = index => {
const reader = new FileReader();
reader.readAsArrayBuffer(fileChunkList[index].file);
reader.onload = e => {
count++;
spark.append(e.target.result);
if (count === fileChunkList.length) {
self.postMessage({
percentage: 100,
hash: spark.end()
});
self.close();
} else {
percentage += 100 / fileChunkList.length;
self.postMessage({
percentage
});
loadNext(count);
}
};
};
loadNext(0);
};
文件合并
當我們的切片全部上傳完畢后,就需要進行文件的合并,這里我們只需要請求接口即可
mergeRequest(data) {
const obj = {
md5: data.fileHash,
fileName: data.name,
fileChunkNum: data.chunkList.length
};
instance.post('fileChunk/merge', obj,
{
timeout: 0
})
.then((res) => {
this.$message.success('上傳成功');
});
}
Done: 至此一個分片上傳的功能便已完成
斷點續傳
顧名思義,就是從那斷的就從那開始,明確思路就很簡單了。一般有2種方式,一種為服務器端返回,告知我從那開始,還有一種是瀏覽器端自行處理。2種方案各有優缺點。本項目使用第二種。
思路:已文件HASH為key值,每個切片上傳成功后,記錄下來便可。若需要續傳時,直接跳過記錄中已存在的便可。本項目將使用Localstorage進行存儲,這里我已提前封裝好addChunkStorage、getChunkStorage方法。
存儲在Stroage的數據
緩存處理
在切片上傳的axios成功回調中,存儲已上傳成功的切片
instance.post('fileChunk', formData, )
.then(res => {
// 存儲已上傳的切片下標
+ this.addChunkStorage(chunkData[index].fileHash, index);
handler();
})
在切片上傳前,先看下localstorage中是否存在已上傳的切片,并修改uploaded
async handleUpload(resume) {
+ const getChunkStorage = this.getChunkStorage(tempFilesArr[i].hash);
tempFilesArr[i].chunkList = fileChunkList.map(({ file }, index) => ({
+ uploaded: getChunkStorage && getChunkStorage.includes(index), // 標識:是否已完成上傳
+ progress: getChunkStorage && getChunkStorage.includes(index) ? 100 : 0,
+ status: getChunkStorage && getChunkStorage.includes(index)? 'success'
+ : 'wait' // 上傳狀態,用作進度狀態顯示
}));
}
構造切片數據時,過濾掉uploaded為true的
async uploadChunks(data) {
var chunkData = data.chunkList;
const requestDataList = chunkData
+ .filter(({ uploaded }) => !uploaded)
.map(({ fileHash, chunk, fileName, index }) => {
const formData = new FormData();
formData.append('md5', fileHash);
formData.append('file', chunk);
formData.append('fileName', index); // 文件名使用切片的下標
return { formData, index, fileName };
})
}
垃圾文件清理
隨著上傳文件的增多,相應的垃圾文件也會增多,比如有些時候上傳一半就不再繼續,或上傳失敗,碎片文件就會增多。解決方案我目前想了2種
前端在localstorage設置緩存時間,超過時間就發送請求通知后端清理碎片文件,同時前端也要清理緩存。
前后端都約定好,每個緩存從生成開始,只能存儲12小時,12小時后自動清理
以上2中方案似乎都有點問題,極有可能造成前后端因時間差,引發切片上傳異常的問題,后面想到合適的解決方案再來更新吧。
Done: 續傳到這里也就完成了。
秒傳
這算是最簡單的,只是聽起來很厲害的樣子。原理:計算整個文件的HASH,在執行上傳操作前,向服務端發送請求,傳遞MD5值,后端進行文件檢索。若服務器中已存在該文件,便不進行后續的任何操作,上傳也便直接結束。大家一看就明白
async handleUpload(resume) {
if (!this.container.files) return;
const filesArr = this.container.files;
var tempFilesArr = this.tempFilesArr;
for (let i = 0; i < tempFilesArr.length; i++) {
const fileChunkList = this.createFileChunk(
filesArr[tempFilesArr[i].index]
);
// hash校驗,是否為秒傳
+ tempFilesArr[i].hash = await this.calculateHash(fileChunkList);
+ const verifyRes = await this.verifyUpload(
+ tempFilesArr[i].name,
+ tempFilesArr[i].hash
+ );
+ if (verifyRes.data.presence) {
+ tempFilesArr[i].status = fileStatus.secondPass;
+ tempFilesArr[i].uploadProgress = 100;
+ } else {
console.log('開始上傳切片文件----》', tempFilesArr[i].name);
await this.uploadChunks(this.tempFilesArr[i]);
}
}
}
// 文件上傳之前的校驗: 校驗文件是否已存在
verifyUpload(fileName, fileHash) {
return new Promise(resolve => {
const obj = {
md5: fileHash,
fileName,
...this.uploadArguments //傳遞其他參數
};
instance
.post('fileChunk/presence', obj)
.then(res => {
resolve(res.data);
})
.catch(err => {
console.log('verifyUpload -> err', err);
});
});
}
Done: 秒傳到這里也就完成了。
后端處理
文章好像有點長了,具體代碼邏輯就先不貼了,除非有人留言要求,嘻嘻,有時間再更新
Node版
請前往 https://github.com/pseudo-god... 查看
JAVA版
下周應該會更新處理
PHP版
1年多沒寫PHP了,抽空我會慢慢補上來
待完善
切片的大?。哼@個后面會做出動態計算的。需要根據當前所上傳文件的大小,自動計算合適的切片大小。避免出現切片過多的情況。
文件追加:目前上傳文件過程中,不能繼續選擇文件加入隊列。(這個沒想好應該怎么處理。)
更新記錄
組件已經運行一段時間了,期間也測試出幾個問題,本來以為沒BUG的,看起來BUG都挺嚴重
BUG-1:當同時上傳多個內容相同但是文件名稱不同的文件時,出現上傳失敗的問題。
預期結果:第一個上傳成功后,后面相同的問文件應該直接秒傳
實際結果:第一個上傳成功后,其余相同的文件都失敗,錯誤信息,塊數不對。
原因:當第一個文件塊上傳完畢后,便立即進行了下一個文件的循環,導致無法及時獲取文件是否已秒傳的狀態,從而導致失敗。
解決方案:在當前文件分片上傳完畢并且請求合并接口完畢后,再進行下一次循環。
將子方法都改為同步方式,mergeRequest 和 uploadChunks 方法
BUG-2: 當每次選擇相同的文件并觸發beforeUpload方法時,若第二次也選擇了相同的文件,beforeUpload方法失效,從而導致整個流程失效。
原因:之前每次選擇文件時,沒有清空上次所選input文件的數據,相同數據的情況下,是不會觸發input的change事件。
解決方案:每次點擊input時,清空數據即可。我順帶優化了下其他的代碼,具體看提交記錄吧。
<input
v-if="!changeDisabled"
type="file"
:multiple="multiple"
class="select-file-input"
:accept="accept"
+ οnclick="f.outerHTML=f.outerHTML"
@change="handleFileChange"/>
重寫了暫停和恢復的功能,實際上,主要是增加了暫停和恢復的狀態
之前的處理邏輯太簡單粗暴,存在諸多問題。現在將狀態定位在每一個文件之上,這樣恢復上傳時,直接跳過即可
封裝組件
寫了一大堆,其實以上代碼你直接復制也無法使用,這里我將此封裝了一個組件。大家可以去github下載文件,里面有使用案例 ,若有用記得隨手給個star,謝謝!
偷個懶,具體封裝組件的代碼就不列出來了,大家直接去下載文件查看,若有不明白的,可留言。
組件文檔
Attribute
參數 類型 說明 默認 備注
headers Object 設置請求頭
before-upload Function 上傳文件前的鉤子,返回false則停止上傳
accept String 接受上傳的文件類型
upload-arguments Object 上傳文件時攜帶的參數
with-credentials Boolean 是否傳遞Cookie false
limit Number 最大允許上傳個數 0 0為不限制
on-exceed Function 文件超出個數限制時的鉤子
multiple Boolean 是否為多選模式 true
base-url String 由于本組件為內置的AXIOS,若你需要走代理,可以直接在這里配置你的基礎路徑
chunk-size Number 每個切片的大小 10M
threads Number 請求的并發數 3 并發數越高,對服務器的性能要求越高,盡可能用默認值即可
chunk-retry Number 錯誤重試次數 3 分片請求的錯誤重試次數
Slot
方法名 說明 參數 備注
header 按鈕區域 無
tip 提示說明文字 無
后端接口文檔:按文檔實現即可
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
千呼萬喚始出來,猶戴口罩半遮面
要說上周什么消息最激動人心,那絕對是全國7月20日低風險地區電影院重開無疑了
消息一出,蟄伏數月的電影愛好者們,紛紛傾巢出動在央媽的評論區里為電影提前打Call。
各種網絡段子也是應聲齊飛:什么待業電影院員工急售餐車搶復工;上座率不超30%,情侶們也得分開坐
想必各位鴨脖聽聞也是激動難耐特別是海報設計師們,終于不用因為沒活兒繼續挨餓了
趁此機會,先來看看電影海報,一來可解相思之苦,順便排一排雷,還能邊挑邊學設計技巧,三全其美,豈不快哉:
1. 誤殺
重映時間—7月20日
近年來難得一見的國產劇情好電影,單從海報上就可見一斑。
「看1000部電影」不能助你飛黃騰達,卻能讓你將世人的眼睛玩弄于股掌,既然生活是一部電影,那就沒有什么是不能人為去剪輯的。
表面上束手就擒的一家人,實則齊心協力,暗度陳倉。
如果你認為孩子畫的案發現場,將成為如山鐵證,那么恭喜你,人都還沒進影院,就已經陷入了導演的敘事詭計。
2. 第一次的離別
上映時間—7月20日
發生在新疆男孩與母親間的溫情故事,母親患病無法言語,更時常離家出走,兩小無猜的男孩女孩,毅然穿越漫漫黃沙,踏上尋母的道路。
沙漠中,只要三個人影在孤獨地行走路的盡頭,究竟是與人別離,還是要跟童年別離,或許,二者都是。
3. 璀璨薪火
上映時間—7月20日
作為非物質文化遺產紀錄片的海報一只飽經風霜的粗糙的手,談不上精妙創意,甚至頗有些質樸。
但別忘了,正是這一雙雙沉穩的手,日復一日,年復一年守護著國人傲立世界的文化的驕傲。
4. 妙先生
上映時間—7月31日
年度話題大作《大護法》的姊妹篇有關「善良」的深刻討論,如果每次拯救惡人,都要犧牲好人,那這種所謂的拯救,究竟還值不值。
溫馨提示,內有大漢「聚眾賞菊」是成年人畫給成年人看的動畫片。
5. 呆瓜兄弟
上映時間—7月31日
從1976年開播的第一集就讓觀眾捧腹大笑的木偶短片。
40年后重聚的不光是這兩只呆瓜更是熒幕前的觀眾與自己的童年。
1. 我想靜靜
上映時間—8月07日
號稱「國內首部動物喜劇電影」海報卻疑似「寵物店」素材打底。
以上動物皆不會出現在電影中怕不光是「首部寵物喜劇」更是「首部魔幻現實主義作品」
2. 蕎麥瘋長
上映時間—8月25日
神似一年一度感動中國的構圖展現著上世紀90年代,三個相互交織串聯的青年人生。
在人們熱衷討論00后、05后的當下導演將鏡頭對準70、80,在建的東方明珠,既是時代的挽歌也是對一代青年人美好未來的贊美。
3. 我在時間盡頭等你
上映時間—8月25日
偶然間獲得的超能力讓男主能夠回到過去,挽救愛情卻被命運捉弄,始終都不得圓滿
與《時空戀旅人》同樣的科幻題材海報當中,也有十分相似的一場雨,希望如同《流浪地球》,國人能再次拍出不輸經典的高水準。
4. 小婦人
上映時間—8月待定
女性可以柔弱,也可以擁有高傲的頭顱南北戰爭中的四姐妹,在清苦生活中活出了不一樣的堅強人生。
5. 急先鋒
上映時間—8月待定
角落里熊熊燃燒的美國核動力航母身后高聳入云的迪拜塔,沒有一處不在透露著這是部大制作。
從沙漠到瀑布,劇情搭不搭且不說至少光是海報就搭了不少真金白銀。
上映時間—8月待定
一心想揭露社會陰暗丑惡的新聞記者處處與人為善的老好人,矛盾的二者相遇,會是場何樣的鬧劇。
上一秒促膝長談,下一秒皮鞋橫飛「鄰里間的美好」或許本身就是偽命題。
6. 許愿神龍
上映時間—8月待定
上海少年誤打誤撞喚醒沉睡神龍卻發自己與龍有著上千年的代溝,西式畫風,中式溫情,一人一龍的冒險故事,值得一看。
7. 無名狂
上映時間—9月25日
一部誕生于摩點眾籌的眾籌電影片名叫《無名狂》,海報卻寫滿了贊助的網友的名字。
圍繞萬歷年間兩大刺客門派所展開的一場場腥風血雨,平靜海報之下,山呼海嘯,風雨欲來。
除此之外,還有不少電影尚未排期,全年隨時可能上映。其中自然也不缺乏優秀的海報創意,比如從路人視角,見證抗戰歷史的《八佰》。
克味十足,容易聯想到哈利波特系列的《刺殺小說家》
單靠海報配色就能讓人陷入迷惑的《抵達之謎》
頗有08年阿迪達斯海報味道的《奪冠》
用風卷云涌展現中式諸神之戰的《封神三部曲》
居然還能拍出第二部的《爵跡2》
正看是海浪,倒看是山巒的《一直游到海水變藍》
近看是懸崖,遠看是槍口的《懸崖之上》
六小齡童老師參與動捕制作今年下半年…可能會上映的《真假美猴王》
希望別播完片頭就播staff名單的《一秒鐘》
能把牛頓從棺材里嚇活過來的《神秘訪客》
進退兩男、男上加男、滿身大漢的《唐人街探案3》
生死一線間淡定到面帶微笑的《緊急救援》
以及萬眾期待的封神宇宙大片《姜子牙》
好了,以上就是2020年將要上映以及可能上映的電影的海報大合集??赐炅诉@些精彩的海報焦急的等待之情是否緩解了一些?
倒也正應了那句老話,只要熬過低谷,后面都是更好的一天。
文章來源:優設 作者:你丫才美工
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
我們應該學習 webpack 嗎 ?
如今,CLI工具(如create-react-app或Vue -cli)已經為我們抽象了大部分配置,并提供了合理的默認設置。
即使那樣,了解幕后工作原理還是有好處的,因為我們遲早需要對默認值進行一些調整。
在本文中中,我們會知道 webpack可以做什么,以及如何配置它以滿足我們的日常需求。
什么是 webpack?
作為前端開發人員,我們應該熟悉 module 概念。 你可能聽說過 AMD模塊,UMD,Common JS還有ES模塊。
webpack是一個模塊綁定器,它對模塊有一個更廣泛的定義,對于webpack來說,模塊是:
Common JS modules
AMD modules
CSS import
Images url
ES modules
webpack 還可以從這些模塊中獲取依賴關系。
webpack 的最終目標是將所有這些不同的源和模塊類型統一起來,從而將所有內容導入JavaScript代碼,并最生成可以運行的代碼。
entry
Webpack的 entry(入口點)是收集前端項目的所有依賴項的起點。 實際上,這是一個簡單的 JavaScript 文件。
這些依賴關系形成一個依賴關系圖。
Webpack 的默認入口點(從版本4開始)是src/index.js,它是可配置的。 webpack 可以有多個入口點。
Output
output是生成的JavaScript和靜態文件的地方。
Loaders
Loaders 是第三方擴展程序,可幫助webpack處理各種文件擴展名。 例如,CSS,圖像或txt文件。
Loaders的目標是在模塊中轉換文件(JavaScript以外的文件)。 文件成為模塊后,webpack可以將其用作項目中的依賴項。
Plugins
插件是第三方擴展,可以更改webpack的工作方式。 例如,有一些用于提取HTML,CSS或設置環境變量的插件。
Mode
webpack 有兩種操作模式:開發(development)和生產(production)。 它們之間的主要區別是生產模式自動生成一些優化后的代碼。
Code splitting
代碼拆分或延遲加載是一種避免生成較大包的優化技術。
通過代碼拆分,開發人員可以決定僅在響應某些用戶交互時加載整個JavaScript塊,比如單擊或路由更改(或其他條件)。
被拆分的一段代碼稱為 chunk。
Webpack入門
開始使用webpack時,先創建一個新文件夾,然后進入該文件中,初始化一個NPM項目,如下所示:
mkdir webpack-tutorial && cd $_
npm init -y
接著安裝 webpack,webpack-cli和 webpack-dev-server:
npm i webpack webpack-cli webpack-dev-server --save-dev
要運行 webpack,只需要在 package.json 配置如下命令即可:
"scripts": {
"dev": "webpack --mode development"
},
通過這個腳本,我們指導webpack在開發模式下工作,方便在本地工作。
Webpack 的第一步
在開發模式下運行 webpack:
npm run dev
運行完后會看到如下錯誤:
ERROR in Entry module not found: Error: Can't resolve './src'
webpack 在這里尋找默認入口點src/index.js,所以我們需要手動創建一下,并輸入一些內容:
mkdir src
echo 'console.log("Hello webpack!")' > src/index.js
現在再次運行npm run dev,錯誤就沒有了。 運行的結果生成了一個名為dist/的新文件夾,其中包含一個名為main.js的 JS 文件:
dist
└── main.js
這是我們的第一個webpack包,也稱為output。
配置 Webpack
對于簡單的任務,webpack無需配置即可工作,但是很快我們就會遇到問題,一些文件如果沒有指定的 loader 是沒法打包的。所以,我們需要對 webpack進行配置,對于 webpack 的配置是在 webpack.config.js 進行的,所以我們需要創建該文件:
touch webpack.config.js
Webpack 用 JavaScript 編寫,并在無頭 JS 環境(例如Node.js)上運行。 在此文件中,至少需要一個module.exports,這是的 Common JS 導出方式:
module.exports = {
//
};
在webpack.config.js中,我們可以通過添加或修改來改變webpack的行為方式
entry point
output
loaders
plugins
code splitting
例如,要更改入口路徑,我們可以這樣做
const path = require("path");
module.exports = {
entry: { index: path.resolve(__dirname, "source", "index.js") }
};
現在,webpack 將在source/index.js中查找要加載的第一個文件。 要更改包的輸出路徑,我們可以這樣做:
const path = require("path");
module.exports = {
output: {
path: path.resolve(__dirname, "build")
}
}
這樣,webpack將把最終生成包放在build中,而不是dist.(為了簡單起見,在本文中,我們使用默認配置)。
打包 HTML
沒有HTML頁面的Web應用程序幾乎沒有用。 要在webpack中使用 HTML,我們需要安裝一個插件html-webpack-plugin:
npm i html-webpack-plugin --save-dev
一旦插件安裝好,我們就可以對其進行配置:
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "src", "index.html")
})
]
};
這里的意思是讓 webpack,從 src/index.html 加載 HTML 模板。
html-webpack-plugin的最終目標有兩個:
加載 html 文件
它將bundle注入到同一個文件中
接著,我們需要在 src/index.html 中創建一個簡單的 HTML 文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Webpack tutorial</title>
</head>
<body>
</body>
</html>
稍后,我們會運行這個程序。
webpack development server
在本文第一部分中,我們安裝了webpack-dev-server。如果你忘記安裝了,現在可以運行下面命令安裝一下:
npm i webpack-dev-server --save-dev
webpack-dev-server 可以讓開發更方便,不需要改動了文件就去手動刷新文件。 配置完成后,我們可以啟動本地服務器來提供文件。
要配置webpack-dev-server,請打開package.json并添加一個 “start” 命令:
"scripts": {
"dev": "webpack --mode development",
"start": "webpack-dev-server --mode development --open",
},
有了 start 命令,我們來跑一下:
npm start
運行后,默認瀏覽器應打開。 在瀏覽器的控制臺中,還應該看到一個 script 標簽,引入的是我們的 main.js。
clipboard.png
使用 webpack loader
Loader是第三方擴展程序,可幫助webpack處理各種文件擴展名。 例如,有用于 CSS,圖像或 txt 文件的加載程序。
下面是一些 loader 配置介紹:
module.exports = {
module: {
rules: [
{
test: /\.filename$/,
use: ["loader-b", "loader-a"]
}
]
},
//
};
相關配置以module 關鍵字開始。 在module內,我們在rules內配置每個加載程序組或單個加載程序。
對于我們想要作為模塊處理的每個文件,我們用test和use配置一個對象
{
test: /\.filename$/,
use: ["loader-b", "loader-a"]
}
test 告訴 webpack “嘿,將此文件名視為一個模塊”。 use 定義將哪些 loaders 應用于些打包的文件。
打包 CSS
要 在webpack 中打包CSS,我們需要至少安裝兩個 loader。Loader 對于幫助 webpack 了解如何處理.css文件是必不可少的。
要在 webpack 中測試 CSS,我們需要在 src 下創建一個style.css文件:
h1 {
color: orange;
}
另外在 src/index.html 添加 h1 標簽
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Webpack tutorial</title>
</head>
<body>
<h1>Hello webpack!</h1>
</body>
</html>
最后,在src/index.js 中加載 CSS:
在測試之前,我們需要安裝兩個 loader:
css-loader: 解析 css 代碼中的 url、@import語法像import和require一樣去處理css里面引入的模塊
style-loader:幫我們直接將css-loader解析后的內容掛載到html頁面當中
安裝 loader:
npm i css-loader style-loader --save-dev
然后在webpack.config.js中配置它們
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "src", "index.html")
})
]
};
現在,如果你運行npm start,會看到樣式表加載在HTML的頭部:
clipboard.png
一旦CSS Loader 就位,我們還可以使用MiniCssExtractPlugin提取CSS文件
Webpack Loader 順序很重要!
在webpack中,Loader 在配置中出現的順序非常重要。以下配置無效:
//
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ["css-loader", "style-loader"]
}
]
},
//
};
此處,“style-loader”出現在 “css-loader” 之前。 但是style-loader用于在頁面中注入樣式,而不是用于加載實際的CSS文件。
相反,以下配置有效:
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
}
]
},
//
};
webpack loaders 是從右到左執行的。
打包 sass
要在 webpack 中測試sass,同樣,我們需要在 src 目錄下創建一個 style.scss 文件:
@import url("https://fonts.googleapis.com/css?family=Karla:weight@400;700&display=swap");
$font: "Karla", sans-serif;
$primary-color: #3e6f9e;
body {
font-family: $font;
color: $primary-color;
}
另外,在src/index.html中添加一些 Dom 元素:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Webpack tutorial</title>
</head>
<body>
<h1>Hello webpack!</h1>
<p>Hello sass!</p>
</body>
</html>
最后,將 sass 文件加載到src/index.js中:
import "./style.scss";
console.log("Hello webpack!");
在測試之前,我們需要安裝幾個 loader:
sass-loader:加載 SASS / SCSS 文件并將其編譯為 CSS
css-loader: 解析 css 代碼中的 url、@import語法像import和require一樣去處理css里面引入的模塊
style-loader:幫我們直接將css-loader解析后的內容掛載到html頁面當中
安裝 loader:
npm i css-loader style-loader sass-loader sass --save-dev
然后在webpack.config.js中配置它們:
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
module.exports = {
module: {
rules: [
{
test: /\.scss$/,
use: ["style-loader", "css-loader", "sass-loader"]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "src", "index.html")
})
]
};
注意loader的出現順序:首先是sass-loader,然后是css-loader,最后是style-loader。
現在,運行npm start,你應該會在HTML的頭部看到加載的樣式表:
clipboard.png
打包現代 JavaScrip
webpack 本身并不知道如何轉換JavaScript代碼。 該任務已外包給babel的第三方 loader,特別是babel-loader。
babel是一個JavaScript編譯器和“編譯器”。 babel 可以將現代JS(es6, es7...)轉換為可以在(幾乎)任何瀏覽器中運行的兼容代碼。
同樣,要使用它,我們需要安裝一些 Loader:
babel-core :把 js 代碼分析成 ast ,方便各個插件分析語法進行相應的處理
babel-preset-env:將現代 JS 編譯為ES5
babel-loader :用于 webpack
引入依賴關系
npm i @babel/core babel-loader @babel/preset-env --save-dev
接著,創建一個新文件babel.config.json配置babel,內容如下:
{
"presets": [
"@babel/preset-env"
]
}
最后在配置一下 webpack :
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
module.exports = {
module: {
rules: [
{
test: /\.scss$/,
use: ["style-loader", "css-loader", "sass-loader"]
},
{
test: /\.js$/,
exclude: /node_modules/,
use: ["babel-loader"]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "src", "index.html")
})
]
};
要測試轉換,可以在 src/index.js中編寫一些現代語法:
import "./style.scss";
console.log("Hello webpack!");
const fancyFunc = () => {
return [1, 2];
};
const [a, b] = fancyFunc();
現在運行npm run dev來查看dist中轉換后的代碼。 打開 dist/main.js并搜索“fancyFunc”:
\n\nvar fancyFunc = function fancyFunc() {\n return [1, 2];\n};\n\nvar _fancyFunc = fancyFunc(),\n _fancyFunc2 = _slicedToArray(_fancyFunc, 2),\n a = _fancyFunc2[0],\n b = _fancyFunc2[1];\n\n//# sourceURL=webpack:///./src/index.js?"
沒有babel,代碼將不會被轉譯:
\n\nconsole.log(\"Hello webpack!\");\n\nconst fancyFunc = () => {\n return [1, 2];\n};\n\nconst [a, b] = fancyFunc();\n\n\n//# sourceURL=webpack:///./src/index.js?");
注意:即使沒有babel,webpack也可以正常工作。 僅在執行 ES5 代碼時才需要進行代碼轉換過程。
在 Webpack 中使用 JS 的模塊
webpack 將整個文件視為模塊。 但是,請不要忘記它的主要目的:加載ES模塊。
ECMAScript模塊(簡稱ES模塊)是一種JavaScript代碼重用的機制,于2015年推出,一經推出就受到前端開發者的喜愛。在2015之年,JavaScript 還沒有一個代碼重用的標準機制。多年來,人們對這方面的規范進行了很多嘗試,導致現在有多種模塊化的方式。
你可能聽說過AMD模塊,UMD,或CommonJS,這些沒有孰優孰劣。最后,在ECMAScript 2015中,ES 模塊出現了。
我們現在有了一個“正式的”模塊系統。
要在 webpack 使用 ES module ,首先創建 src/common/usersAPI.js 文件:
const ENDPOINT = "https://jsonplaceholder.typicode.com/users/";
export function getUsers() {
return fetch(ENDPOINT)
.then(response => {
if (!response.ok) throw Error(response.statusText);
return response.json();
})
.then(json => json);
}
在 src/index.js中,引入上面的模塊:
import { getUsers } from "./common/usersAPI";
import "./style.scss";
console.log("Hello webpack!");
getUsers().then(json => console.log(json));
生產方式
如前所述,webpack有兩種操作模式:開發(development )和(production)。 到目前為止,我們僅在開發模式下工作。
在開發模式中,為了便于代碼調試方便我們快速定位錯誤,不會壓縮混淆源代碼。相反,在生產模式下,webpac k進行了許多優化:
使用 TerserWebpackPlugin 進行縮小以減小 bundle 的大小
使用ModuleConcatenationPlugin提升作用域
在生產模式下配 置webpack,請打開 package.json 并添加一個“ build” 命令:
現在運行 npm run build,webpack 會生成一個壓縮的包。
Code splitting
代碼拆分(Code splitting)是指針對以下方面的優化技術:
避免出現一個很大的 bundle
避免重復的依賴關系
webpack 社區考慮到應用程序的初始 bundle 的最大大小有一個限制:200KB。
在 webpack 中有三種激活 code splitting 的主要方法:
有多個入口點
使用 optimization.splitChunks 選項
動態導入
第一種基于多個入口點的技術適用于較小的項目,但是從長遠來看它是不可擴展的。這里我們只關注第二和第三種方式。
Code splitting 與 optimization.splitChunks
考慮一個使用Moment.js 的 JS 應用程序,Moment.js是流行的時間和日期JS庫。
在項目文件夾中安裝該庫:
npm i moment
現在清除src/index.js的內容,并引入 moment 庫:
import moment from "moment";
運行 npm run build 并查看控制的輸出內容:
main.js 350 KiB 0 [emitted] [big] main
整個 moment 庫都綁定到了 main.js 中這樣是不好的。借助optimization.splitChunks,我們可以從主包中移出moment.js。
要使用它,需要在 webpack.config.js 添加 optimization 選項:
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
module.exports = {
module: {
// ...
},
optimization: {
splitChunks: { chunks: "all" }
},
// ...
};
運行npm run build 并查看運行結果:
main.js 5.05 KiB 0 [emitted] main
vendors~main.js 346 KiB 1 [emitted] [big] vendors~main
現在,我們有了一個帶有moment.js 的vendors?main.js,而主入口點的大小更合理。
注意:即使進行代碼拆分,moment.js仍然是一個體積較大的庫。 有更好的選擇,如使用luxon或date-fns。
Code splitting 與 動態導入
Code splitting的一種更強大的技術使用動態導入來有條件地加載代碼。 在ECMAScript 2020中提供此功能之前,webpack 提供了動態導入。
這種方法在 Vue 和 React 之類的現代前端庫中得到了廣泛使用(React有其自己的方式,但是概念是相同的)。
Code splitting 可用于:
模塊級別
路由級別
例如,你可以有條件地加載一些 JavaScript 模塊,以響應用戶的交互(例如單擊或鼠標移動)。 或者,可以在響應路由更改時加載代碼的相關部分。
要使用動態導入,我們先清除src/index.html,并寫入下面的內容:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Dynamic imports</title>
</head>
<body>
<button id="btn">Load!</button>
</body>
</html>
在 src/common/usersAPI.js中:
const ENDPOINT = "https://jsonplaceholder.typicode.com/users/";
export function getUsers() {
return fetch(ENDPOINT)
.then(response => {
if (!response.ok) throw Error(response.statusText);
return response.json();
})
.then(json => json);
}
在 src/index.js 中
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
//
});
如果運行npm run start查看并單擊界面中的按鈕,什么也不會發生。
現在想象一下,我們想在某人單擊按鈕后加載用戶列表。 “原生”的方法可以使用靜態導入從src/common /usersAPI.js加載函數:
import { getUsers } from "./common/usersAPI";
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
getUsers().then(json => console.log(json));
});
問題在于ES模塊是靜態的,這意味著我們無法在運行時更改導入的內容。
通過動態導入,我們可以選擇何時加載代碼
const getUserModule = () => import("./common/usersAPI");
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
getUserModule().then(({ getUsers }) => {
getUsers().then(json => console.log(json));
});
});
這里我們創建一個函數來動態加載模塊
const getUserModule = () => import("./common/usersAPI");
現在,當你第一次使用npm run start加載頁面時,會看到控制臺中已加載 js 包:
clipboard.png
現在,僅在單擊按鈕時才加載/common/usersAPI:
clipboard.png
對應的 chunk 是 0.js
通過在導入路徑前面加上魔法注釋/ * webpackChunkName:“ name_here” * /,可以更改塊名稱:
const getUserModule = () =>
import(/* webpackChunkName: "usersAPI" */ "./common/usersAPI");
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
getUserModule().then(({ getUsers }) => {
getUsers().then(json => console.log(json));
});
});
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
準備工作
//借助插件
npm install babel-plugin-import --save-dev
// .babelrc
{
"plugins": [["import", {
"libraryName": "view-design",
"libraryDirectory": "src/components"
}]]
}
在main.js中引入
import "view-design/dist/styles/iview.css";
import { Button, Table } from "view-design";
const viewDesign = {
Button: Button,
Table: Table
};
Object.keys(viewDesign).forEach(element => {
Vue.component(element, viewDesign[element]);
});
先用google瀏覽器打開正常,以上操作猛如虎,IE瀏覽器打開250,好了不廢話,下面是解決方案
解決方案
//vue.config.js中配置
chainWebpack: config => {
//解決iview 按需引入babel轉換問題
config.module
.rule("view-design") // 我目前用的是新版本的iview ,舊版本的iview,用iview代替view-design
.test(/view-design.src.*?js$/)
.use("babel")
.loader("babel-loader")
.end();
}
問題原因
為什么會有如上問題呢? 這個就和babel轉換問題有關了,按需引入時,那些組件里js文件未進行babel轉換或轉換不徹底就被引入了,ie11對es6+的語法支持是很差的,所以以上方法就是讓引入文件前就對view-design的src下的所有js文件進行babel轉換,舉一反三,當按需引入第三方框架時出現這個問題,都可用這方法解決了,只要把規則和正則中view-design進行替換。
延伸擴展
//全局引入
import ViewUI from "view-design";
Vue.use(ViewUI);
import "view-design/dist/styles/iview.css";
tips:在全局引入時,一定要記住不要在.babelrc文件里配置按需導入,會導致沖突
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
用戶隱私安全在產品設計中是很重要的一個環節,本文從用戶體驗角度切入,從匿名模式、減少永久性和減少公開性三個方面展開分析。
我們先看?組來??優的2019年6?的調研數據:70%的美國?認為,與5年前相?個?信息變得更不安全。尤其是?學歷?收?群體。由此可??戶對個?信息數據的隱私擔憂?以往更甚。
?戶隱私安全很重要,涉及的范圍和?度也很多。本次的分析從?戶體驗?度切?,涉及如下三個??:
下圖是Google系App(Google AppChromeYouTubeandGoogle Map)匿名模式切換,從交互體驗上來說有如下?個特點:
匿名模式不是最近才流?的功能,最早提供隱匿模式的是蘋果safari瀏覽器,早在 2005年就?持了匿名模式。Chrome瀏覽器在2008年就開始?持此模式。雖然由來已久,為什么到了2020年,匿名模式依然是國外互聯???趨勢呢?
我們看?組數據:
這是來?DuckDuckGo 2019年9?的調研(DuckDuckGo是美國的?款不記錄?戶?為保護?戶隱私的搜索引擎)。樣本來?美國、英國、德國和澳?利亞的成年??戶,共計3,411?的調研得出。各國?戶對使?搜索引擎的個?隱私安全?常在意(是否搜集個?的數據和記錄搜索?為)。
2020年5?DuckDuckGo?均搜索次數為6200萬。對?看2019年11?底?均搜索次數4900萬,2018年10?是2900萬。
最近?年的持續活躍度?幅增?證明了不記錄個?隱私的搜素引擎越來越受到?戶的?睞。
國內,頭條、UC瀏覽器在搜索框輸?狀態也提供了「?痕瀏覽」??。
不僅是搜索引擎領域,保護?戶隱私也成為Facebook最重要的戰略?向之?。Facebook CEO Mark Zuckerberg在2019年 F8開發者?會上喊出「THE FUTURE IS PRIVATE」。2019年3?Mark Zuckerberg發?,主題就是《聚焦于保護隱私的社交?絡》。
我們先看國外社交媒體Stories(?故事)產品形態的流?。
?們總是對于所分享的內容永遠記錄在?上感到擔憂。Stories 24?時消失緩解了?們的隱私顧慮,這讓?戶更安?地?然分享。
Stories由Snapchat?創,由 Facebook發揚光?。早在2019年4?,Facebook+Messenger Stories, Instagram Stories?活?戶數就突破5億。 2020年2-3?LinkedIn,Twitter也先后宣布將上線類似功能。
來??優的調研報告:41%的美國?經歷過?絡騷擾,最常?的就是在社交媒體上。23%的?戶最近經歷的?絡騷擾來?評論區的評論內容。27%的?戶經歷?絡騷擾后決定不再發布任何內容。
我們以限定評論互動的公開性為例:
2020年5?Twitter上線了新的評論功能,可以限定誰可以回復帖?的功能,提供了三種選項:誰都可以評論,只有被關注者可以評論,只有被提及者可以評論三種公開度的限定。
Instagram也在測試「評論限制」新功能,批量屏蔽/限制評論。?前已經上線的?個例?:?戶(評論發布者)如果發布的評論含有攻擊性敏感詞,發布前伴有提示,提醒評論含有攻擊性敏感詞是否真的要發布。
注重隱私提供僅好友可?/僅??可?/僅作者可?/等多重維度的隱私設定有助于?戶更安?地參與互動。
另外?個例?是付費頻道會員:付費頻道會員-限定頻道的公開性讓內容創作者減輕隱私顧慮不僅能獲得?告收?,也能得到來?會員、會費的收?,形成「忠實粉絲」社區,有助于內容?態的社區化建設。
我們主要看YouTube的頻道會員案例:
YouTube有兩種會員模式。?種是YouTube整個平臺的付費會員,去?告,看原創美劇影視,消費?樂,可下載內容的模式。第?種模式是Youtuber個?頻道付費會員,吸引忠實粉絲加?。我想說的就是第?種。
為什么?V?紅有意愿開通頻道會員?
除了獲得忠實粉絲收?變現的商業價值以及付費頻道會員可以為忠實粉絲提供各種專屬功能,背后也和?紅?V對個?隱私顧慮有關。
?紅?V在完全公開的社交?絡上需要始終保持?夠克制謹慎,避免引起爭議。但在忠實粉絲付費頻道專屬會員群中,?紅?V會減輕隱私顧慮,更加回歸?我。
?如在頻道會員中發布更多與個??活相關的內容,表達更多不便在完全公開的社交?絡中的想法和感受等,因為忠實粉絲通常更具包容度,更不容易引起爭議。
YouTube頻道會員費?可以從三種會費(按?)區間選擇,?持多選:
頻道會員功能在2018年開始測試,?向粉絲數過10萬的YouTuber開放。
以上綜述,我們可以說:
1. 匿名模式:
雖然匿名模式由來已久,但仍然是當前的??基本?戶體驗設計趨勢,尤其是匿名模式的切換便捷性?常重要。
2. 減少永久性:
Stories?故事24?時消失緩解了?們的隱私顧慮,這讓?戶更安?地?然分享,已經成為國外社交媒體平臺的必備功能,Facebook, Instagram平臺的最主要、最具影響?的功能之?。
3. 減少公開性:
?戶總是對在社交媒體平臺發表評論有所顧忌,限定評論的公開性能夠有助于促進?戶發帖表達,其他?戶也可以更安?地參與互動。
付費頻道會員可以限定頻道的公開性,讓內容創作者減輕隱私顧慮不僅能獲得?告收?,也能得到來?會員會費的收?,形成「忠實粉絲」社區,有助于內容?態的社區化建設。
從UE?度,我們可以為頻道會員提供專屬身份設計例如專屬徽章,專屬表情等。
THE FUTURE IS PRIVATE, 注重?戶隱私的體驗設計越來越重要!
文章來源:優設 作者:
百度MEUX
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
最近在項目中遇到這樣一個問題
當頁面加載完畢后由于選項卡的另外兩張屬于display:none;狀態 所以另外兩張選項卡內echarts的寬高都會變成默認100*100
查閱了很多網上的案例,得出一下一些解決方案:
1:
原因很簡單,在tab頁中,圖表的父容器div是隱藏的(display:none),圖表在執行js初始化的時候找不到這個元素,所以自動將“100%”轉成了“100”,最后計算出來的圖表就成了100px
解決辦法:
找一個在tab頁的切換操作中不會隱藏的父容器,把它的寬度的具體值取出后在初始化圖表之前直接賦給圖表
1 $("#chartMain").css('width',$("#TabContent").width());//獲取父容器的寬度具體數值直接賦值給圖表以達到寬度100%的效果 2 var Chart = echarts.init(document.getElementById('chartMain')); 3 4 // 指定圖表的配置項和數據 5 option = { ...配置項和數據 }; 6 7 // 使用剛指定的配置項和數據顯示圖表。 8 Chart.setOption(option);
2:mychart.resize() 重新渲染高度
3: 后來我想到了問題所在,既然高度是因為display:none;導致的 那大可不必設置這個屬性,但是在頁面渲染完畢后加上即可
所以取消了選項卡的display:none; 但在頁面加載完畢后
window.οnlοad=function(){
根基id在添加css display:none;
}
即可解決,
分割線
---------------------------------------------------------------------
接下來解決一下ifram內外通訊 互相通訊賦值ifram src 和高度問題
統一資源定位符,縮寫為URL,是對網絡資源(網頁、圖像、文件)的引用。URL指定資源位置和檢索資源的機制(http、ftp、mailto)。
舉個例子,這里是這篇文章的 URL 地址:
https://dmitripavlutin.com/parse-url-javascript
很多時候你需要獲取到一段 URL 的某個組成部分。它們可能是 hostname(例如 dmitripavlutin.com),或者 pathname(例如 /parse-url-javascript)。
一個方便的用于獲取 URL 組成部分的辦法是通過 URL() 構造函數。
在這篇文章中,我將給大家展示一段 URL 的結構,以及它的主要組成部分。
接著,我會告訴你如何使用 URL() 構造函數來輕松獲取 URL 的組成部分,比如 hostname,pathname,query 或者 hash。
1. URL 結構
一圖勝千言。不需要過多的文字描述,通過下面的圖片你就可以理解一段 URL 的各個組成部分:
image
2. URL() 構造函數
URL() 構造函數允許我們用它來解析一段 URL:
const url = new URL(relativeOrAbsolute [, absoluteBase]);
參數 relativeOrAbsolute 既可以是絕對路徑,也可以是相對路徑。如果第一個參數是相對路徑的話,那么第二個參數 absoluteBase 則必傳,且必須為第一個參數的絕對路徑。
舉個例子,讓我們用一個絕對路徑的 URL 來初始化 URL() 函數:
const url = new URL('http://example.com/path/index.html');
url.href; // => 'http://example.com/path/index.html'
或者我們可以使用相對路徑和絕對路徑:
const url = new URL('/path/index.html', 'http://example.com');
url.href; // => 'http://example.com/path/index.html'
URL() 實例中的 href 屬性返回了完整的 URL 字符串。
在新建了 URL() 的實例以后,你可以用它來訪問前文圖片中的任意 URL 組成部分。作為參考,下面是 URL() 實例的接口列表:
interface URL {
href: USVString;
protocol: USVString;
username: USVString;
password: USVString;
host: USVString;
hostname: USVString;
port: USVString;
pathname: USVString;
search: USVString;
hash: USVString;
readonly origin: USVString;
readonly searchParams: URLSearchParams;
toJSON(): USVString;
}
上述的 USVString 參數在 JavaScript 中會映射成字符串。
3. Query 字符串
url.search 可以獲取到 URL 當中 ? 后面的 query 字符串:
const url = new URL(
'http://example.com/path/index.html?message=hello&who=world'
);
url.search; // => '?message=hello&who=world'
如果 query 參數不存在,url.search 默認會返回一個空字符串 '':
const url1 = new URL('http://example.com/path/index.html');
const url2 = new URL('http://example.com/path/index.html?');
url1.search; // => ''
url2.search; // => ''
3.1 解析 query 字符串
相比于獲得原生的 query 字符串,更實用的場景是獲取到具體的 query 參數。
獲取具體 query 參數的一個簡單的方法是利用 url.searchParams 屬性。這個屬性是 URLSearchParams 的實例。
URLSearchParams 對象提供了許多用于獲取 query 參數的方法,如get(param),has(param)等。
下面來看個例子:
const url = new URL(
'http://example.com/path/index.html?message=hello&who=world'
);
url.searchParams.get('message'); // => 'hello'
url.searchParams.get('missing'); // => null
url.searchParams.get('message') 返回了 message 這個 query 參數的值——hello。
如果使用 url.searchParams.get('missing') 來獲取一個不存在的參數,則得到一個 null。
4. hostname
url.hostname 屬性返回一段 URL 的 hostname 部分:
const url = new URL('http://example.com/path/index.html');
url.hostname; // => 'example.com'
5. pathname
url. pathname 屬性返回一段 URL 的 pathname 部分:
const url = new URL('http://example.com/path/index.html?param=value');
url.pathname; // => '/path/index.html'
如果這段 URL 不含 path,則該屬性返回一個斜杠 /:
const url = new URL('http://example.com/');
url.pathname; // => '/'
6. hash
最后,我們可以通過 url.hash 屬性來獲取 URL 中的 hash 值:
const url = new URL('http://example.com/path/index.html#bottom');
url.hash; // => '#bottom'
當 URL 中的 hash 不存在時,url.hash 屬性會返回一個空字符串 '':
const url = new URL('http://example.com/path/index.html');
url.hash; // => ''
7. URL 校驗
當使用 new URL() 構造函數來新建實例的時候,作為一種副作用,它同時也會對 URL 進行校驗。如果 URL 不合法,則會拋出一個 TypeError。
舉個例子,http ://example.com 是一段非法 URL,因為它在 http 后面多寫了一個空格。
讓我們用這個非法 URL 來初始化 URL() 構造函數:
try {
const url = new URL('http ://example.com');
} catch (error) {
error; // => TypeError, "Failed to construct URL: Invalid URL"
}
因為 http ://example.com 是一段非法 URL,跟我們想的一樣,new URL() 拋出了一個 TypeError。
8. 修改 URL
除了獲取 URL 的組成部分以外,像 search,hostname,pathname 和 hash 這些屬性都是可寫的——這也意味著你可以修改 URL。
舉個例子,讓我們把一段 URL 從 red.com 修改成 blue.io:
const url = new URL('http://red.com/path/index.html');
url.href; // => 'http://red.com/path/index.html'
url.hostname = 'blue.io';
url.href; // => 'http://blue.io/path/index.html'
注意,在 URL() 實例中只有 origin 和 searchParams 屬性是只讀的,其他所有的屬性都是可寫的,并且會修改原來的 URL。
9. 總結
URL() 構造函數是 JavaScript 中的一個能夠很方便地用于解析(或者校驗)URL 的工具。
new URL(relativeOrAbsolute [, absoluteBase]) 中的第一個參數接收 URL 的絕對路徑或者相對路徑。當第一個參數是相對路徑時,第二個參數必傳且必須為第一個參數的基路徑。
在新建 URL() 的實例以后,你就能很輕易地獲得 URL 當中的大部分組成部分了,比如:
url.search 獲取原生的 query 字符串
url.searchParams 通過 URLSearchParams 的實例去獲取具體的 query 參數
url.hostname獲取 hostname
url.pathname 獲取 pathname
url.hash 獲取 hash 值
那么你最愛用的解析 URL 的 JavaScript 工具又是什么呢?
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
藍藍設計的小編 http://www.syprn.cn