1. 加載和執行
盡量將所有的<script>標簽放在</body>標簽之前,確保腳本執行前頁面已經完成了渲染,避免腳本的下載阻塞其他資源(例如圖片)的下載。
合并腳本,減少頁面中的<script>標簽
使用<script>標簽的defer和async屬性(兩者的區別見這里)
通過Javascript動態創建<script>標簽插入文檔來下載,其不會影響頁面其他進程
2.數據存取
由于作用域鏈的機制,訪問局部變量比訪問跨作用域變量更快,因此在函數中若要多次訪問跨作用域變量,則可以用局部變量保存。
避免使用with語句,其會延長作用域鏈
嵌套的對象成員會導致引擎搜索所有對象成員,避免使用嵌套,例如window.location.href
對象的屬性和方法在原型鏈的位置越深,訪問的速度也越慢
3.Dom編程
進行大段HTML更新時,推薦使用innerHTML,而不是DOM方法
HTML集合是一個與文檔中元素綁定的類數組對象,其長度隨著文檔中元素的增減而動態變化,因此避免在每次循環中直接讀取HTML集合的length,容易導致死循環
使用節點的children屬性,而不是childNodes屬性,前者訪問速度更快,且不包含空白文本和注釋節點。
瀏覽器的渲染過程包括構建DOM樹和渲染樹,當DOM元素的幾何屬性變化時,需要重新構造渲染樹,這一過程稱為“重排”,完成重排后,瀏覽器會重新繪制受影響的部分到屏幕中,這一過程稱為“重繪”。因此應該盡量合并多次對DOM的修改,或者先將元素脫離文檔流(display:none、文檔片段),應用修改后,再插入文檔中。
每次瀏覽器的重排時都會產生消耗,大多數瀏覽器會通過隊列化修改并批量執行來優化重排過程,可當訪問元素offsetTop、scrollTop、clientTop、getComputedStyle等一系列布局屬性時,會強制瀏覽器立即進行重排返回正確的值。因此不要在dom布局信息改變時,訪問這些布局屬性。
當修改同個元素多個Css屬性時,可以使用CssText屬性進行一次性修改樣式,減少瀏覽器重排和重繪的次數
當元素發生動畫時,可以使用絕對定位使其脫離文檔流,動畫結束后,再恢復定位。避免動畫過程中瀏覽器反復重排文檔流中的元素。
多使用事件委托,減少監聽事件
4.算法和流程控制
for循環和while循環性能差不多,除了for-in循環最慢(其要遍歷原型鏈)
循環中要減少對象成員及數組項的查詢次數,可以通過倒序循環提高性能
循環次數大于1000時,可運用Duff Devices減少迭代次數
switch比if-else快,但如果具有很多離散值時,可使用數組或對象來構建查找表
遞歸可能會造成調用棧溢出,可將其改為循環迭代
如果可以,對一些函數的計算結果進行緩存
5.字符串和正則表達式
進行大量字符串的連接時,+和+=效率比數組的join方法要高
當創建了一個正則表達式對象時,瀏覽器會驗證你的表達式,然后將其轉化為一個原生代碼程序,用戶執行匹配工作。當你將其賦值給變量時,可以避免重復執行該步驟。
當正則進入使用狀態時,首先要確定目標字符串的起始搜索位置(字符串的起始位置或正則表達式的lastIndex屬性),之后正則表達式會逐個檢查文本和正則模式,當一個特定的字元匹配失敗時,正則表達式會試著回溯到之前嘗試匹配的位置,然后嘗試其他路徑。如果正則表達式所有的可能路徑都沒有匹配到,其會將起始搜索位置下移一位,重新開始檢查。如果字符串的每個字符都經歷過檢查,沒有匹配成功,則宣布徹底失敗。
當正則表達式不那么具體時,例如.和[\s\S]等,很可能會出現回溯失控的情況,在js中可以應用預查模擬原子組(?=(pattern))\1來避免不必要的回溯。除此之外,嵌套的量詞,例如/(A+A+)+B/在匹配"AAAAAAAA"時可能會造成驚人的回溯,應盡量避免使用嵌套的量詞或使用預查模擬原子組消除回溯問題。
將復雜的正則表達式拆分為多個簡單的片段、正則以簡單、必需的字元開始、減少分支數量|,有助于提高匹配的效率。
setTimeout(function(){ process(todo.shift()); if (todo.length > 0) { setTimeout(arguments.callee, 25); } else { callback(); } })
setTimeout(function(){ let start = +new Date(); do { process(todo.shift()); } while(todo.length > 0 && (+new Date() - start) < 50) if (todo.length > 0) { setTimeout(arguments.callee, 25); } else { callback(); } })
WebWork
進行計算
Expires: Mon,28 Jul 2018 23:30:30 GMT
eval
、Function
進行雙重求值
Object
/Array
字面量定義,不要使用構造函數
if (i & 1) { className = 'odd'; } else { className = 'even'; }
Math
對象等
背景
這一個因為滾動條占據空間引起的bug, 查了一下資料, 最后也解決了,順便研究一下這個屬性, 做一下總結,分享給大家看看。
正文
昨天, 測試提了個問題, 現象是一個輸入框的聚焦提示偏了, 讓我修一下, 如下圖:
image.png
起初認為是紅框提示位置不對, 就去找代碼看:
<Input
// ...
onFocus={() => setFocusedInputName('guidePrice')}
onBlur={() => setFocusedInputName('')}
/>
<Table
data-focused-column={focusedInputName}
// ...
/>
代碼上沒有什么問題, 不是手動設置的,而且, 在我和另一個同事, 還有PM的PC上都是OK的:
image.png
初步判斷是,紅框位置結算有差異, 差異大小大概是17px, 但是這個差異是怎么產生的呢?
就去測試小哥的PC上看, 注意到一個細節, 在我PC上, 滾動條是懸浮的:
image.png
在他PC上, 滾動條是占空間的:
image.png
在他電腦上, 手動把原本的 overscroll-y: scroll 改成 overscroll-y: overlay 問題就結局了。
由此判定是: 滾動條占據空間 引起的bug。
overscroll-y: overlay
CSS屬性 overflow, 定義當一個元素的內容太大而無法適應塊級格式化上下文的時候該做什么。它是 overflow-x 和overflow-y的 簡寫屬性 。
/* 默認值。內容不會被修剪,會呈現在元素框之外 */
overflow: visible;
/* 內容會被修剪,并且其余內容不可見 */
overflow: hidden;
/* 內容會被修剪,瀏覽器會顯示滾動條以便查看其余內容 */
overflow: scroll;
/* 由瀏覽器定奪,如果內容被修剪,就會顯示滾動條 */
overflow: auto;
/* 規定從父元素繼承overflow屬性的值 */
overflow: inherit;
官方描述:
overlay 行為與 auto 相同,但滾動條繪制在內容之上而不是占用空間。 僅在基于 WebKit(例如,Safari)和基于Blink的(例如,Chrome或Opera)瀏覽器中受支持。
表現:
html {
overflow-y: overlay;
}
兼容性
沒有在caniuse上找到這個屬性的兼容性, 也有人提這個問題:
image.png
問題場景以及解決辦法
1. 外部容器的滾動條
這里的外部容器指的是html, 直接加在最外層:
html {
overflow-y: scroll;
}
手動加上這個特性, 不論什么時候都有滾動寬度占據空間。
缺點: 沒有滾動的時候也會有個滾動條, 不太美觀。
優點: 方便, 沒有兼容性的問題。
2. 外部容器絕對定位法
用絕對定位,保證了body的寬度一直保持完整空間:
html {
overflow-y: scroll; // 兼容ie8,不支持:root, vw
}
:root {
overflow-y: auto;
overflow-x: hidden;
}
:root body {
position: absolute;
}
body {
width: 100vw;
overflow: hidden;
}
3. 內部容器做兼容
.wrapper {
overflow-y: scroll; // fallback
overflow-y: overlay;
}
總結
個人推薦還是用 overlay, 然后使用scroll 做為兜底。
內容就這么多, 希望對大家有所啟發。
文章如有錯誤, 請在留言區指正, 謝謝。
之前花了些時間將gatsby-theme-gitbook遷移到 Typescript,以獲得在 VSCode 中更好的編程體驗.
整體差不多已經完成遷移,剩下將 Gatsby 的 API 文件也遷移到 TS,這里可以看到 gatsby#21995 官方也在將核心代碼庫遷移到 Typescript,準備等待官方將核心代碼庫遷移完成,在遷移 API 文件.
這篇文章用XYShaoKang/gatsby-project-config,演示如何將 gatsby 遷移到 TypeScript,希望能幫到同樣想要在 Gatsby 中使用 TS 的同學.
遷移步驟:
TS 配置
配置 ESLint 支持 TS
完善 GraphQL 類型提示
初始化項目
gatsby new gatsby-migrate-to-typescript XYShaoKang/gatsby-project-config
cd gatsby-migrate-to-typescript
yarn develop
TS 配置
安裝typescript
添加typescript.json配置文件
修改 js 文件為 tsx
補全 TS 聲明定義
安裝typescript
yarn add -D typescript
添加配置文件tsconfig.json
// https://www.typescriptlang.org/v2/docs/handbook/tsconfig-json.html
{
"compilerOptions": {
"target": "esnext", // 編譯生成的目標 es 版本,可以根據需要設置
"module": "esnext", // 編譯生成的目標模塊系統
"lib": ["dom", "es2015", "es2017"], // 配置需要包含的運行環境的類型定義
"jsx": "react", // 配置 .tsx 文件的輸出模式
"strict": true, // 開啟嚴格模式
"esModuleInterop": true, // 兼容 CommonJS 和 ES Module
"moduleResolution": "node", // 配置模塊的解析規則,支持 node 模塊解析規則
"noUnusedLocals": true, // 報告未使用的局部變量的錯誤
"noUnusedParameters": true, // 報告有關函數中未使用參數的錯誤
"experimentalDecorators": true, // 啟用裝飾器
"emitDecoratorMetadata": true, // 支持裝飾器上生成元數據,用來進行反射之類的操作
"noEmit": true, // 不輸出 js,源映射或聲明之類的文件,單純用來檢查錯誤
"skipLibCheck": true // 跳過聲明文件的類型檢查,只會檢查已引用的部分
},
"exclude": ["./node_modules", "./public", "./.cache"], // 解析時,應該跳過的路晉
"include": ["src"] // 定義包含的路徑,定義在其中的聲明文件都會被解析進 vscode 的智能提示
}
將index.js改成index.tsx,重新啟動服務,查看效果.
其實 Gatsby 內置了支持 TS,不用其他配置,只要把index.js改成index.tsx就可以直接運行.添加 TS 依賴是為了顯示管理 TS,而tsconfig.json也是這個目的,當我們有需要新的特性以及自定義配置時,可以手動添加.
補全 TS 聲明定義
打開index.tsx,VSCode 會報兩個錯誤,一個是找不到styled-components的聲明文件,這個可以通過安裝@types/styled-components來解決.
另外一個錯誤綁定元素“data”隱式具有“any”類型。,這個錯誤是因為我們在tsconfig.json中指定了"strict": true,這會開啟嚴格的類型檢查,可以通過關閉這個選項來解決,只是我們用 TS 就是要用它的類型檢查的,所以正確的做法是給data定義類型.
下面來一一修復錯誤.
安裝styled-components的聲明文件
yarn add -D @types/styled-components
修改index.tsx
import React, { FC } from 'react'
import styled from 'styled-components'
import { graphql } from 'gatsby'
import { HomeQuery } from './__generated__/HomeQuery'
const Title = styled.h1`
font-size: 1.5em;
margin: 0;
padding: 0.5em 0;
color: palevioletred;
background: papayawhip;
`
const Content = styled.div`
margin-top: 0.5em;
`
interface PageQuery {
data: {
allMarkdownRemark: {
edges: Array<{
node: {
frontmatter: {
title: string
}
excerpt: string
}
}>
}
}
}
const Home: FC<PageQuery> = ({ data }) => {
const node = data.allMarkdownRemark.edges[0].node
const title = node.frontmatter?.title
const excerpt = node.excerpt
return (
<>
<Title>{title}</Title>
<Content>{excerpt}</Content>
</>
)
}
export default Home
export const query = graphql`
query HomeQuery {
allMarkdownRemark {
edges {
node {
frontmatter {
title
}
excerpt
}
}
}
}
`
這時候會出現一個新的錯誤,在excerpt: string處提示Parsing error: Unexpected token,這是因為 ESLint 還無法識別 TS 的語法,下面來配置 ESLint 支持 TS.
配置 ESLint 支持 TypeScript
安裝依賴
yarn add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
配置.eslintrc.js
module.exports = {
parser: `@typescript-eslint/parser`, // 將解析器從`babel-eslint`替換成`@typescript-eslint/parser`,用以解析 TS 代碼
extends: [
`google`,
`eslint:recommended`,
`plugin:@typescript-eslint/recommended`, // 使用 @typescript-eslint/eslint-plugin 推薦配置
`plugin:react/recommended`,
`prettier/@typescript-eslint`, // 禁用 @typescript-eslint/eslint-plugin 中與 prettier 沖突的規則
`plugin:prettier/recommended`,
],
plugins: [
`@typescript-eslint`, // 處理 TS 語法規則
`react`,
`filenames`,
],
// ...
}
在.vscode/settings.json中添加配置,讓VSCode使用ESLint擴展格式化ts和tsx文件
// .vscode/settings.json
{
"eslint.format.enable": true,
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[javascriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[typescriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
}
}
完善 GraphQL 類型提示
// index.tsx
import React, { FC } from 'react'
// ...
interface PageQuery {
data: {
allMarkdownRemark: {
edges: Array<{
node: {
frontmatter: {
title: string
}
excerpt: string
}
}>
}
}
}
const Home: FC<PageQuery> = ({ data }) => {
// ...
}
export default Home
export const query = graphql`
query HomeQuery {
allMarkdownRemark {
edges {
node {
frontmatter {
title
}
excerpt
}
}
}
}
`
我們看看index.tsx文件,會發現PropTypes和query結構非常類似,在Gatsby運行時,會把query查詢的結果作為組件prop.data傳入組件,而PropTypes是用來約束prop存在的.所以其實PropTypes就是根據query寫出來的.
如果有依據query自動生成PropTypes的功能就太棒了.
另外一個問題是在query中編寫GraphQL查詢時,并沒有類型約束,也沒有智能提示.
總結以下需要完善的體驗包括:
GraphQL 查詢編寫時的智能提示,以及錯誤檢查
能夠從 GraphQL 查詢生成對應的 TypeScript 類型.這樣能保證類型的唯一事實來源,并消除 TS 中冗余的類型聲明.畢竟如果經常需要手動更新兩處類型,會更容易出錯,而且也并不能保證手動定義類型的正確性.
實現方式:
通過生成架構文件,配合Apollo GraphQL for VS Code插件,實現智能提示,以及錯誤檢查
通過graphql-code-generator或者apollo生成 TS 類型定義文件
如果自己去配置的話,是挺耗費時間的,需要去了解graphql-code-generator的使用,以及Apollo的架構等知識.
不過好在社區中已經有對應的 Gatsby 插件集成了上述工具可以直接使用,能讓我們不用去深究對應知識的情況下,達到優化 GraphQL 編程的體驗.
嘗試過以下兩個插件能解決上述問題,可以任選其一使用
gatsby-plugin-codegen
gatsby-plugin-typegen
另外還有一款插件gatsby-plugin-graphql-codegen也可以生成 TS 類型,不過配置略麻煩,并且上述兩個插件都可以滿足我現在的需求,所以沒有去嘗試,感興趣的可以嘗試一下.
注意點:
Apollo不支持匿名查詢,需要使用命名查詢
第一次生成,需要運行Gatsby之后才能生成類型文件
整個項目內不能有相同命名的查詢,不然會因為名字有沖突而生成失敗
下面是具體操作
安裝vscode-apollo擴展
在 VSCode 中按 Ctrl + P ( MAC 下: Cmd + P) 輸入以下命令,按回車安裝
ext install apollographql.vscode-apollo
方式一: 使用gatsby-plugin-codegen
gatsby-plugin-codegen默認會生成apollo.config.js和schema.json,配合vscode-apollo擴展,可以提供GraphQL的類型約束和智能提示.
另外會自動根據query中的GraphQL查詢,生成 TS 類型,放在對應的tsx文件同級目錄下的__generated__文件夾,使用時只需要引入即可.
如果需要在運行時自動生成 TS 類型,需要添加watch: true配置.
安裝gatsby-plugin-codegen
yarn add gatsby-plugin-codegen
配置gatsby-config.js
// gatsby-config.js
module.exports = {
plugins: [
// ...
{
resolve: `gatsby-plugin-codegen`,
options: {
watch: true,
},
},
],
}
重新運行開發服務生成類型文件
yarn develop
如果出現以下錯誤,一般是因為沒有為查詢命名的緣故,給查詢添加命名即可,另外配置正確的話,打開對應的文件,有匿名查詢,編輯器會有錯誤提示.
fix-anonymous-operations.png
這個命名之后會作為生成的類型名.
修改index.tsx以使用生成的類型
gatsby-plugin-codegen插件會更具查詢生成對應的查詢名稱的類型,保存在對應tsx文件同級的__generated__目錄下.
import { HomeQuery } from './__generated__/HomeQuery' // 引入自動生成的類型
// ...
// interface PageQuery {
// data: {
// allMarkdownRemark: {
// edges: Array<{
// node: {
// frontmatter: {
// title: string
// }
// excerpt: string
// }
// }>
// }
// }
// }
interface PageQuery {
data: HomeQuery // 替換之前手寫的類型
}
// ...
將自動生成的文件添加到.gitignore中
apollo.config.js,schema.json,__generated__能通過運行時生成,所以可以添加到.gitignore中,不用提交到 git 中.當然如果有需要也可以選擇提交到 git 中.
# Generated types by gatsby-plugin-codegen
__generated__
apollo.config.js
schema.json
方式二: 使用gatsby-plugin-typegen
gatsby-plugin-typegen通過配置生成gatsby-schema.graphql和gatsby-plugin-documents.graphql配合手動創建的apollo.config.js提供GraphQL的類型約束和智能提示.
根據GraphQL查詢生成gatsby-types.d.ts,生成的類型放在命名空間GatsbyTypes下,使用時通過GatsbyTypes.HomeQueryQuery來引入,HomeQueryQuery是由對應的命名查詢生成
安裝gatsby-plugin-typegen
yarn add gatsby-plugin-typegen
配置
// gatsby-config.js
module.exports = {
plugins: [
// ...
{
resolve: `gatsby-plugin-typegen`,
options: {
outputPath: `src/__generated__/gatsby-types.d.ts`,
emitSchema: {
'src/__generated__/gatsby-schema.graphql': true,
},
emitPluginDocuments: {
'src/__generated__/gatsby-plugin-documents.graphql': true,
},
},
},
],
}
//apollo.config.js
module.exports = {
client: {
tagName: `graphql`,
includes: [
`./src/**/*.{ts,tsx}`,
`./src/__generated__/gatsby-plugin-documents.graphql`,
],
service: {
name: `GatsbyJS`,
localSchemaFile: `./src/__generated__/gatsby-schema.graphql`,
},
},
}
重新運行開發服務生成類型文件
yarn develop
修改index.tsx以使用生成的類型
gatsby-plugin-codegen插件會更具查詢生成對應的查詢名稱的類型,保存在對應tsx文件同級的__generated__目錄下.
// ...
// interface PageQuery {
// data: {
// allMarkdownRemark: {
// edges: Array<{
// node: {
// frontmatter: {
// title: string
// }
// excerpt: string
// }
// }>
// }
// }
// }
interface PageQuery {
data: GatsbyTypes.HomeQueryQuery // 替換之前手寫的類型
}
// ...
將自動生成的文件添加到.gitignore中
__generated__能通過運行時生成,所以可以添加到.gitignore中,不用提交到 git 中.當然如果有需要也可以選擇提交到 git 中.
# Generated types by gatsby-plugin-codegen
__generated__
Canvas 是 HTML5 提供的一個用于展示繪圖效果的標簽. Canvas 原意為畫布, 在 HTML 頁面中用于展示繪圖效果. 最早 Canvas 是蘋果提出的一個方案, 今天已經在大多數瀏覽器中實現。
canvas 的使用領域
游戲
大數據可視化數據
banner 廣告
多媒體
模擬仿真
遠程操作
圖形編輯
判斷瀏覽器是否支持 canvas 標簽
var canvas = document.getElementById('canvas')
if (canvas.getContext) {
console.log('你的瀏覽器支持Canvas!')
} else {
console.log('你的瀏覽器不支持Canvas!')
}
canvas 的基本用法
1、使用 canvas 標簽, 即可在頁面中開辟一格區域,可以設置其寬高,寬高為 300 和 150
<canvas></canvas>
2、獲取 dom 元素 canvas
canvas 本身不能繪圖. 是使用 JavaScript 來完成繪圖. canvas 對象提供了各種繪圖用的 api。
var cas = document.querySelector('canvas')
3、通過 cas 獲取上下文對象(畫布對象!)
var ctx = cas.getContext('2d')
4、通過 ctx 開始畫畫(設置起點 設置終點 連線-描邊 )
ctx.moveTo(10, 10)
ctx.lineTo(100, 100)
ctx.stroke()
繪制線條
設置開始位置: context.moveTo( x, y )
設置終點位置: context.lineTo( x, y )
描邊繪制: context.stroke()
填充繪制: context.fill()
閉合路徑: context.closePath()
canvas 還可以設置線條的相關屬性,如下:
CanvasRenderingContext2D.lineWidth 設置線寬.
CanvasRenderingContext2D.strokeStyle 設置線條顏色.
CanvasRenderingContext2D.lineCap 設置線末端類型,'butt'( 默認 ), 'round', 'square'.
CanvasRenderingContext2D.lineJoin 設置相交線的拐點, 'miter'(默認),'round', 'bevel',
CanvasRenderingContext2D.getLineDash() 獲得線段樣式數組.
CanvasRenderingContext2D.setLineDash() 設置線段樣式.
CanvasRenderingContext2D.lineDashOffset 繪制線段偏移量.
封裝一個畫矩形的方法
function myRect(ctxTmp, x, y, w, h) {
ctxTmp.moveTo(x, y)
ctxTmp.lineTo(x + w, y)
ctxTmp.lineTo(x + w, y + h)
ctxTmp.lineTo(x, y + h)
ctxTmp.lineTo(x, y)
ctxTmp.stroke()
}
var cas = document.querySelector('canvas')
var ctx = cas.getContext('2d')
myRect(ctx, 50, 50, 200, 200)
繪制矩形
fillRect( x , y , width , height) 填充以(x,y)為起點寬高分別為 width、height 的矩形 默認為黑色
stokeRect( x , y , width , height) 繪制一個空心以(x,y)為起點寬高分別為 width、height 的矩形
clearRect( x, y , width , height ) 清除以(x,y)為起點寬高分別為 width、height 的矩形 為透明
繪制圓弧
繪制圓弧的方法有
CanvasRenderingContext2D.arc()
CanvasRenderingContext2D.arcTo()
6 個參數: x,y(圓心的坐標),半徑,起始的弧度(不是角度 deg),結束的弧度,(bool 設置方向 ! )
var cas = document.querySelector('canvas')
var ctx = cas.getContext('2d')
ctx.arc(100, 100, 100, 0, degToArc(360))
ctx.stroke()
// 角度轉弧度
function degToArc(num) {
return (Math.PI / 180) * num
}
繪制扇形
var cas = document.querySelector('canvas')
var ctx = cas.getContext('2d')
ctx.arc(300, 300, 200, degToArc(125), degToArc(300))
// 自動連回原點
ctx.closePath()
ctx.stroke()
function degToArc(num) {
return (Math.PI / 180) * num
}
制作畫筆
聲明一個變量作為標識
鼠標按下的時候,記錄起點位置
鼠標移動的時候,開始描繪并連線
鼠標抬起的時候,關閉開關
點擊查看效果圖
var cas = document.querySelector('canvas')
var ctx = cas.getContext('2d')
var isDraw = false
// 鼠標按下事件
cas.addEventListener('mousedown', function () {
isDraw = true
ctx.beginPath()
})
// 鼠標移動事件
cas.addEventListener('mousemove', function (e) {
if (!isDraw) {
// 沒有按下
return
}
// 獲取相對于容器內的坐標
var x = e.offsetX
var y = e.offsetY
ctx.lineTo(x, y)
ctx.stroke()
})
cas.addEventListener('mouseup', function () {
// 關閉開關了!
isDraw = false
})
手動涂擦
原理和畫布相似,只不過用的是clearRect()方法。
點擊查看效果圖
var cas = document.querySelector('canvas')
var ctx = cas.getContext('2d')
ctx.fillRect(0, 0, 600, 600)
// 開關
var isClear = false
cas.addEventListener('mousedown', function () {
isClear = true
})
cas.addEventListener('mousemove', function (e) {
if (!isClear) {
return
}
var x = e.offsetX
var y = e.offsetY
var w = 20
var h = 20
ctx.clearRect(x, y, w, h)
})
cas.addEventListener('mouseup', function () {
isClear = false
})
刮刮樂
首先需要設置獎品和畫布,將畫布置于圖片上方蓋住,
隨機設置生成獎品。
當手觸摸移動的時候,可以擦除部分畫布,露出獎品區。
點擊查看效果圖
<div>
<img src="./images/2.jpg" alt="" />
<canvas width="600" height="600"></canvas>
</div>
css
img {
width: 600px;
height: 600px;
position: absolute;
top: 10%;
left: 30%;
}
canvas {
width: 600px;
height: 600px;
position: absolute;
top: 10%;
left: 30%;
border: 1px solid #000;
}
js
var cas = document.querySelector('canvas')
var ctx = cas.getContext('2d')
var img = document.querySelector('img')
// 加一個遮罩層
ctx.fillStyle = '#ccc'
ctx.fillRect(0, 0, cas.width, cas.height)
setImgUrl()
// 開關
var isClear = false
cas.addEventListener('mousedown', function () {
isClear = true
})
cas.addEventListener('mousemove', function (e) {
if (!isClear) {
return
}
var x = e.offsetX
var y = e.offsetY
ctx.clearRect(x, y, 30, 30)
})
cas.addEventListener('mouseup', function () {
isClear = false
})
function setImgUrl() {
var arr = ['./images/1.jpg', './images/2.jpg', './images/3.jpg', './images/4.jpg']
// 0-3
var random = Math.round(Math.random() * 3)
img.src = arr[random]
}
更多demo,請查看 github.com/Michael-lzg…
簡單來說,v-if 的初始化較快,但切換代價高;v-show 初始化慢,但切換成本低
都是動態顯示DOM元素
(1)手段:
v-if是動態的向DOM樹內添加或者刪除DOM元素;
v-show是通過設置DOM元素的display樣式屬性控制顯隱;
(2)編譯過程:
v-if切換有一個局部編譯/卸載的過程,切換過程中合適地銷毀和重建內部的事件監聽和子組件;
v-show只是簡單的基于css切換;
(3)編譯條件:
v-if是惰性的,如果初始條件為假,則什么也不做;只有在條件第一次變為真時才開始局部編譯(編譯被緩存?編譯被緩存后,然后再切換的時候進行局部卸載);
v-show是在任何條件下(首次條件是否為真)都被編譯,然后被緩存,而且DOM元素保留;
(4)性能消耗:
v-if有更高的切換消耗;
v-show有更高的初始渲染消耗;
(5)使用場景:
v-if適合運營條件不大可能改變;
v-show適合頻繁切換。
雖然Vue 3還沒有正式發布,但是熱愛新技術的我早已按捺不住自己的內心,開始嘗試在小項目中使用它了。
根據這篇《今日凌晨Vue3 beta版震撼發布,竟然公開支持腳手架項目!》我搭建了一個Vue 3的腳手架項目,用這種方式搭建的腳手架項目不僅僅只有vue是新版的,就連vue-router、vuex都是的。
給大家截一下package.json的圖:
可以看到vue-router和vuex都已經開啟4.0時代啦!
不過其實我并沒有去了解過vue-router 4.0的新用法什么的,因為我覺得它不像vue 3.0都已經進行到beta的版本不會有特別大的變動。
而vue-router 4.0還是alpha的階段,所以我認為現在去學習它有些為時尚早。但卻就是它!差點釀成了一場慘劇。
舊版vue + vue-router的使用方式
假如你在路由里面定義了一個動態參數通常都會這么寫:
{
path: '/:id'
}
然后用編程式導航的時候通常會這樣去寫:
this.$router.push('/123')
在組件中是這樣獲取這個參數的:
this.$route.params.id
我以為的新版vue + vue-router的使用方式
由于vue 3.0的Composition API中沒有this了,所以我想到了通過獲取組件實例的方式來獲取$route:
import { defineComponent, getCurrentInstance } from 'vue'
export default defineComponent((props, context) => {
const { ctx } = getCurrentInstance()
console.log(ctx.$route)
})
沒想到打印出來的居然是undefined!
這是咋回事呢?
于是我又打印了一遍ctx(ctx是當前組件上下文):
沒有$的那些字段是我在組件中自己定義的變量,帶$的這些就是vue內置的了,找了半天發現沒有$route了,只剩下了一個$router,估計vue-router 4.0把當前路由信息都轉移到$router里面去了。
帶著猜想,我點開了$router:
currentRoute! 看名字的話感覺應該就是它了!于是乎我:
import { defineComponent, getCurrentInstance } from 'vue'
export default defineComponent((props, context) => {
const { ctx } = getCurrentInstance()
console.log(ctx.$router.currentRoute.value.params.id)
})
果然獲取到了!好開心!
實際的新版vue + vue-router用法
在接下來的過程中我用ctx.$router代替了原來的this.$router、用ctx.$router.currentRoute.value代替了原先的this.$route。
盡管在接下來的進度中并沒有出現任何的bug,程序一直都是按照我所設想的那樣去運行的。
但在項目打包后卻出現了意想不到的bug:在跳轉路由的時候報了一個在undefined上面沒有push的錯誤。
奇了怪了,在開發階段程序都沒有任何的報錯怎么一打包就不行了呢?根據我多年的開發經驗,我很快就定位到了是vue-router的錯誤。
難道這樣寫是錯的嗎?可是我打印了ctx,它里面明明有一個$router、$router里面明明就有currentRoute、currentRoute里面明明就有一個value、value里面明明就有params、params里面我一點開明明就看到了傳過來的參數啊:
估計可能是vue-router的bug,果然alpha階段的產物不靠譜,我開始后悔使用新版的vue腳手架項目了。
vue-router里的hooks
不過這時我突然靈光一現,vue 3不是受到了react hooks的啟發才產生了Composition API的嗎?
那么估計vue-router肯定也會受到react-router的啟發了!
還好我學過react,果然技多不壓身?。」烙嬂锩婵隙ㄊ怯幸粋€useXxx,就像這樣:
import { useXxx } from 'vue-router'
那么應該是use什么呢?按理來說應該會盡量的和以前的API保持一定的聯系,我猜應該是useRoute和useRouter吧!
為了驗證我的想法,我打開了node_modules找到了vue-router的源碼:
果不其然,在第2454和第2455行我發現它導出了useRoute和useRouter,那么就是它了:
import { defineComponent } from 'vue'
import { useRoute, useRouter } from 'vue-router'
export default defineComponent(_ => {
const route = useRoute()
const router = useRouter()
console.log(route.params.id)
router.push('/xxx/xxx')
})
使用這種方式不但可以成功跳轉路由,也同樣可以獲取到路由傳過來的參數,這次再打包試了一下,果然就沒有之前的那個報錯了。
結語
估計以后的vue全家桶要開啟全民hooks的時代了,在翻看源碼的同時我發現他們把一些示例都寫在了vue-router/playground文件夾下了,在里面我發現了一些有趣的用法。
如果有時間的話我會仔細研究一下然后出一篇更加深入的文章給大家,當然如果已經有小伙伴等不及我出新文章的話可以直接進入vue-router-next的github地址:
https://github.com/vuejs/vue-router-next
它的示例都放在了playground這個文件夾下,期待你們研究明白后出一篇更加深入的文章!
將要分析的競品排了個期,從最難最不熟悉的開始。為什么從最難的開始,可能是個人習慣吧,吃掉最難的那個,后面就會更上手。突然想起之前讀的一本書「吃掉那只青蛙」,很不錯的一本書,有時間去溫習下。
一個產品,其實會有很多功能點,有核心的主要功能,也有一些輔助功能,也會有一些讓你忽略,但關鍵時刻很需要的應急功能,而這些點都需要去整理出來。
這一點很重要,要先熟悉產品。如果對產品都不熟悉,那還是先不要做競品分析。因為很難判斷競品的功能和風格是否也適合當前產品,因為對產品的不熟悉,會產生誤判。
當然,產品的目標人群,產品定位,適用范圍等等,都會影響產品分析。
所以,花時間熟悉自己負責的產品,是不能跳過的。
1. 制定時間規劃
最好事先做好時間規劃,可以有一整塊的時間,這樣分析產品時,思緒也會比較完整和連續,可以更專注。計算大概分析一個產品需要花費的時間,最好不要用零碎時間來做,這樣只會增加時間上的代價,也會增加挫折感;
2. 確定分析的目的
在「競品分析」中,想要得到的結論和重點是什么。比如重點可能是產品的報表功能、產品的代碼審核功能等等,目的的確定能讓分析更有針對性,減少干擾。無目的隨意分析,得到的結果也會是零亂不堪,最后只是在浪費時間。
3. 尋找幫助者
每個產品,都有其不一樣的特性和產品邏輯,你不一定能夠完全 cover 到,甚至有些點就是比較難理解的,特別是偏技術性的名詞,這時若有技術同學的幫助,就會如虎添翼。所以最好可以事先找一位產品相關的技術同學,詢問這段時間是否有空,幫助你解答一些問題。
個人建議:能夠在網上查到的資料,就不要先問人,除非時間成本特別高。一方面也是提升自己解決問題的能力,另一方面,也是節省彼此的時間。對方愿意幫你解決問題,不代表你要把所有問題一股腦倒給他,自己了解后再問,也是對對方的尊重,大家的時間都同樣寶貴。
4. 其他tips
如果是內部公司產品,提前確認是否需要權限,提前申請好,減少正式開始后,還要等待審批時間。外部產品可以提前找好網站,可以咨詢的客服入口,如果是付費競品,咨詢是否可以向財務申請報銷等等。
1. 像個用戶一樣去使用產品
很多時候,設計師的職業病,會讓我們過多注重視覺享受,而忽略作為用戶,想要的有時候只是功能可用。今天不管你把「掃一掃」功能做得多美,美得像個藝術品一樣,可是當掃碼付款的時候,怎么也掃不出來,那種站在店家前面忐忑不安,怎么也無法完成付款,后面一堆人等你,你仿佛聽見后面其他顧客竊竊私語地討論著發生什么事情。那種場景我相信你不想經歷,同樣我們也不應該讓用戶來經歷。
我的項目主管,一直都有提醒我,要像個小白來使用和設計我們的產品。這句建議,也一直在提醒著我。如果站在高姿態來俯視用戶,我們就很難真正的「懂」用戶,進而很難設計出真正滿足用戶需要的產品。
這是競品分析,但是我們也需要轉換自己的角色,變成用戶。這樣能更明白究竟競品帶給用戶是便利,還是麻煩。有時適時抽離「設計師」的角色,會讓你更能去體會用戶的感受。
所以,先去用這個產品吧,然后才會有然后。
2. 如何去使用競品
一個產品的使用,總是有它的使用場景,手機端的就更多樣了,簡直無所不在。B 端產品可能會相對少,一般是在辦公場景或是特定場景。
可以像個編劇一樣,給自己寫點劇本,加點情節,塑造一個角色,假設競品是電商方向,你可以想像,自己是一個剛畢業的社會新人,你可能沒多少錢,你可能剛拿到你人生第一桶金,你想買件衣服犒勞自己,或許你會是數碼控,你關注已久的佳能單反在雙 11 中有優惠等等,然后再去預想接下去的情節,在購物方面會考慮的問題,或許是好用,或許是有趣等等。
也可以做任務式去使用產品,比如以電商為例,任務可以是買件喜歡的衣服,從搜索產品,到找到喜歡的衣服,添加購物車,提交訂單,等待發貨,收貨,確認收貨。這一個完整的流程走下來,就會體驗產品功能是否好用,搜索結果是否符合預期等等。
3. 記錄
使用產品的過程中,會遇到很多情況,有些是可預期的,有些是不可預期的。有些讓人覺得很好用,有些卻會讓人受挫。將這些情況都記錄下來,有助于分析產品的可用性程度和滿意度。
上面這些只是舉例說明,在競品當中可能遇到的一些問題,也可以去反思自己的產品是否也會這樣讓用戶感到困惑。有時候,太熟悉自己的產品,會自認為產品很完美,會理所當然認為「大家都這么認為」……
記錄問題、原因,感受并截圖為證(有必要可錄屏),后期可追溯。寫得越詳細越好,后面整理的時候會更清晰。
4. 各個擊破-功能了解
在熟悉整個產品后,就需要對產品的各個功能進行分析了解、梳理。了解競品的核心功能是什么,核心功能在解決用戶什么問題,是否真的解決了用戶的痛點,其他功能又在整個產品當中充當什么樣的角色。
將競品的功能與本產品功能對比,不只是對比有無,更進一步地去想,為什么有這個功能,為什么沒有這個功能,有或沒有是否會提高用戶的使用效率,用戶的留存,用戶的體驗等等。
功能多不代表好,如果功能不能給用戶帶來益處,其實它的存在只是增加開發成本而已。
其實競品分析中,最難的是總結歸納。做了一堆的分析后,結論是什么呢,這個結論如何寫呢?
可以先從設立分析目的開始,找到中心軸線,然后再慢慢延展開來。在要做總結報告時,你會欣喜地發現最初設立目標是多么的重要。
文章來源:優設 作者:箴鹽設計
財務和金融相關的應用是一個相對專業的分支,在這個領域當中創造體驗優異的設計并不是一件簡單的事情。身為資深設計師Taras Bakusevych 在這篇文章當中,分享了10個確保這類產品足夠優秀的核心設計原則。
互聯網時代的人們早就受夠了信息爆炸,我們每天都會經系統推送、應用通知、微信、電話、短信等各類渠道收到大量消息。有多久你沒有查收自己的郵箱?就算打開郵件,又有多少推薦內容讓你有興趣進一步了解?是 EDM 老了沒用了?真正的原因,可能是我們一開始就錯誤地忽視了 EDM 設計。
對于 95 后以及更年輕的群體來說,EDM 確實是個上了年紀的概念。EDM(Email Direct Marketing)也叫 Email 營銷、電子郵件營銷。企業向目標客戶發送 EDM 郵件,建立同目標客戶的溝通渠道,向其直接傳達相關信息,用來促進銷售轉化。
這個起源于上世紀 80 年代中期,正式誕生于 90 年代的早期互聯網產物現在已經三十多歲了。時至今日,EDM 早已成為了全球公認的網絡營銷重要方法之一,其卓越效果為互聯網人數十年的實踐所證實。但 EDM 在我國的應用還處于非常低級的水平,不僅沒有系統的理論,在實踐中也存在許多誤區。
在這樣一個重視審美與強調更新及時的時代,EDM 郵件樸實無華的外表與「一旦發出就固定呈現」的內容特質顯得有些格格不入。作為用戶體驗設計師,我們可以做什么讓 EDM 不落伍呢?
首先,我們可以在設計層面上避免 EDM 郵件被郵箱軟件識別為垃圾郵件,不帶敏感詞語或內容、淡化商業廣告色彩、減少數字與附件使用都有助于降低被郵箱系統屏蔽的風險。我們更可以在全量發送前,對指定郵箱進行小范圍測試以確保郵件發送成功率。
其次,從其歷史來源來看,早期的 EDM 來源于垃圾郵件,這使人們對其本能地缺乏好感,存在排斥心理。因此 EDM 的節奏和時機必須做好控制,對郵件發送的各類數據做好統計,掌握用戶的閱讀習慣,能更好地提升郵件的打開率。
郵件內容需要設計為一定的格式來發送,常用的郵件格式包括純文本格式、HTML 格式和 Rich Media 格式,或者是這些格式的組合。一般來說,HTML 格式和 Rich Media 格式的電子郵件比純文本格式具有更好的體驗效果。但 Rich Media 格式的電子郵件易造成郵件過大,并且無法確保用戶在客戶端均能夠正常顯示,所以在設計時我們優先選擇 HTML 格式郵件。
與網頁不同,我們無法針對不同設備做郵件內容相應的適配設計,兼顧設備特性的通用模版也就成為了設計時的必要關注點。對用戶來說,一封郵件閱讀體驗很差,那么無論郵件的內容多么精彩、多么吸引人,最終的結果也可能只會被丟棄在一邊。因此,我們通常會按照移動端尺寸對郵件界面進行設計,注意字體大小、最佳尺寸以及鏈接按鈕的大小等。
除此以外,郵件中鏈接的定義也應得到我們充分的重視。由于郵件中的鏈接我們同樣無法預先針對不同打開設備進行單獨編輯,在有條件的情況下我們可以對鏈接所跳轉的頁面進行響應式設計以確保高質量的跨端瀏覽體驗,或者我們也可以采用默認跳轉路徑而后重定向的傳統方式。
EDM 營銷與一般的營銷方式最大的區別是:EDM 是一對一的溝通,讓用戶感覺到尊重,讓他感覺到這是為他所建立并且是他所獨享的溝通方式。在標題、正文的文案上強調「我」,在內容上也應如此。用戶在意什么,我們就發送什么。把握住用戶關注的信息,幫助用戶收集支持 TA 做決策所需的信息。當我們發送郵件給用戶,給予其操作行為的反饋或提醒時,不要浪費這最好的營銷機會。優先提供給用戶與之行為或特征相關的服務與幫助,其次通過個性化服務或產品推薦促進購買或注冊轉化,有助于我們將營銷機會轉化為實際銷售成果。
做好個性化對 EDM 內容模型要求頗高,但從設計角度講,我們完全可以以原子設計思維實現郵件內容模塊的低成本創建與復用。以通用設計模塊為「殼」,內容與組合規則為「核」,快速響應 EDM 的運營需求。
以上 5 點就是我結合近期項目經驗所得。EDM 雖老,但設計可以讓 EDM 老而彌新。祝經你精心設計的 EDM 郵件,一經發出,封封有回應
文章來源:優設 作者:魚子醬聊設計
今天和大家聊一個很多朋友常年卡在 P5/P6 需要關心的命題——如何從業務出發打造具有商業價值還能兼顧用戶體驗的設計,此篇不談理論,就通過 4 個經典的重量級產品案例就給大家安排明白啥是「一拳超人」式體驗設計——就一個字「強」。
滴滴出行應該屬于大家的高頻使用 app,但是使用的功能一般還是集中在叫車流程,所以大家可能不太會關注到 CDX 設計團隊一個非常核心的設計成果——xpanel。
簡單來說 xpanel 就是一個附著于第一信息架構層級上,垂直 Y 軸且支持 X 軸拓展滑動的 Feed 卡片位。內容上分為「消息卡片」「主體卡片」「拓展卡片」三個維度,首屏保障除了「消息」與「主體」外三分之一「拓展卡片1」的露出。
但在簡單的交互背后蘊藏的是基于業務的 UGD(用戶增長設計)設計思考,這里引用 2018IXDC 會上滴滴主講人的原話來說就是:
對特定場景垂直領域的深耕和挖掘,尋找「接觸點」,幫助獲取更多的功能、內容、服務、特性、品牌、運營甚至是喜好……進而實現業務的「有效增長」(轉化、變現、留存)。
通俗一點解釋就是 xpanel 利用主卡與拓展卡之間的信息架構關系,把拓展卡平衡的分為幾類,比如「與產品功能相關的卡片」「與運營相關的卡片」等。
把本來被 LBS 地圖一屏內搶占的空間通過簡易的交互模式補償回來了,這樣既不打破用戶的核心體驗 focus 在地圖與主卡上,同時又增強了運營、功能的玩法與拓展,可謂雙贏。
根據這幾年滴滴 xpanel 的線上應用,拓展卡片基本挖掘涵蓋了以下場景的露出:優惠福利、出現卡券、會員體系、安全相關、出行提醒、拉新導流、運營活動等,未來可拓展的價值內容會更多??粗髀烦鲂蓄?app 又紛紛長期沿用 xpanel 的設計,想必線上的數據反饋應該也是很正向的。
在上篇文章《多維度解析 | 抖音vs快手的產品設計策略差異》中的商業化模塊里簡要提及過抖音的 Topiew 超級廣告位,這里單獨拿出來和大家解析一下它究竟有多6。
從功能角度看,它是一個從開屏延續到端內視頻信息流的廣告位,占據了用戶從進入抖音的第一視覺。
從交互角度看,topview 主要展現以開屏沉浸式視頻 3s 播放→淡出互動轉化組件 3s(完美融入原生視頻信息流),剩余操作手勢與功能等同原生視頻信息流。
在這樣一個有著 1 億+第一曝光的產品位置,單純只做常規靜態開屏穩當入賬不香嗎?事實是抖音確實讓它不香了,沒有創新就沒有新的收獲。基于業務和當前產品形態下的交互模式使抖音有一個天時地利的優勢——沉浸式體驗,在這樣的交互模式下給視頻化的開屏提供了很好的承接入口。從開屏開啟到融入信息流,在交互形態的切換中又為廣告內容的播放時長贏得了更多時間。
更可怕的一點是 3s 播放后融入原生視頻信息流中的 TopView 除了正常收割廣告轉化帶來的單量,還可以通過右側的主頁鏈接輕松引流進行粉絲沉淀(今天就算你不買,先關注我,成為我的潛在用戶,來日我再推一個新商品視頻,你可以第一時間看見也許感興趣就買單了)。
說完這些大家仔細回憶一下平常我們接觸的有視頻廣告的視頻平臺,別說 60s、30s,15s 我們都嫌長,但為啥 TopView 顯得相對沒那么惹人煩呢(上次留的思考題)?個人認為除了抖音在選擇合作品牌時會傾向符合平臺氣質的品牌合作(細數它合作過的品牌:Mac、寶馬、林肯、vivo 等)保障廣告質量和提供「跳過」外,直接融入信息淡出的互動組件會不僅會給用戶新奇感,還會激發用戶的互動欲望。
最后看一組數據(與寶馬合作數據),曝光數:1.1 億+;有效播放率:53.82%;點擊率:13.26%。所以你猜一個最長可以展示 60s 的品牌視頻內容、同時進行品牌粉絲沉淀、良好體驗帶來更高有效播放的億級曝光廣告位能值多少錢?
2016 年淘寶啟動了一個項目要做一款內容化欄目——以視頻為主,每晚更新一期,類比「一千零一夜」的故事。
那么在滿滿當當的淘寶運營區里該選擇哪一個來試玩這個有趣的「新欄目」呢?是在頭部的 10 宮格里再擠進去一個圖標呢?還是在熱門推薦里擠出一個 tab 呢?還是做一個懸浮的右下角的運營位?顯然都不太合適。
根據這款產品每晚 6 點鐘才可以使用,早上 7 點就會消失的游戲規則,最適配它的入口是一個不占界面原生空間,同時又有一定儀式感的位置。于是下拉 loading 的大空區成為了設計師們考慮的陣地。
△ 不知道這個banner為什么要排擠我
但地方選好了,又有了新顧慮。因為 iOS 的用戶基本被系統洗腦了下拉手勢,對于他們來說下拉=刷新,貿然在下拉刷新的手勢基礎上再疊加一個無關聯的結果顯然是有風險的。因此從交互上需要界定 2 個維度的指標來保障新欄目的體驗。
反復試錯 2 個指標數據的實際體驗之后,新欄目有了安身之所,賜名「二樓」。進入「二樓」的整體交互和現在的短視頻產品玩法基本雷同,全屏豎滑切換,小圖標帶貨。下拉加載位的開發,從普通 loading 動效到運營位的植入基本被各類電商平臺輕松復刻了,因此這一切看上去更沒什么了得,但對于原創來說那畢竟是 4 年前。
談到豆瓣我算是半個老用戶了,豆瓣自身是個比較復雜的集合多條業務線分支(「小組」「同城」「閱讀」「音影」……)的多生態產品,這里我們主要拿它 18 年 6.0 大改版中影音模塊的詳情頁大改造來說事兒。
△ 可能有很多人已經忘記6.0前的豆瓣電影詳情頁長啥樣了,帶你回顧一下。
看完對比圖,視力正常的朋友乍一看都能看出 6.0 版詳情頁整容得有多成功。但具體成功在哪里,可能不僅僅是好看這么簡單。
大背景從海報上智能取色雖然不算是什么稀奇的做法,但是加了適度的漸變應用在這里也可以說是非常的恰到好處了。另外深底色和視覺比重加大的外鏈區都突顯了「第三方播放」與「購票選座」的視覺感知。讓用戶沉浸在電影詳情中并引導他們走向「豆瓣的主要收入來源之一——電影票分銷與第三方視頻播放產品引流」正好是 6.0 豆瓣改版一個「小小的目標」——更務實(商業化)。
從交互層面看,且不說評論頭部吸底這個事情是不是也是因為 6.0 商業化的影響(評論區增加「話題」進行重點運營),這個交互本身我覺得還是很強大的。強大的體現在于良好的空間收納能力與信息拓展能力。我給它起了個好聽的名字叫-疊加上滑板(不好聽也認了吧,畢竟也沒有內部人員告訴我他們是不是起名字了)
這里可能又會有很多人質疑它與用戶已洗腦的上滑手勢之間的沖突,這點解釋起來和上文淘寶「二樓」有些類似,區別是豆瓣并沒有做上滑速度 or 距離的臨界值,只是把滑動區域做了隔離。而對比它的效仿者 boss 直聘,人家倒是在交互上做了進一步優化,適配自己的產品情況做了上滑疊層卡隱藏和上滑距離臨界值。
這個故事告訴我們,要抄也要抄得比人家的交互更優秀才不丟人昂。
文章來源:優設 作者:Nana的設計錦囊
藍藍設計的小編 http://www.syprn.cn