1. 前言
之前惡搞了一張朋友的表情包,直接在百度上找了一個線上表情包製作器,突然靈光一閃,要是支援攝像頭該多好,方便又快捷 (重點是省手機記憶體,不用拍照 ? ),二話不說,開始搬磚
2. 預想的功能點
圖片支援直接貼上 和 拖拽
圖片和文字縮放,支援滑鼠滾輪
支援圖片翻轉
支援捕捉攝像頭畫面當素材
3. 擼頁面
使用的第三方庫
antd
宇宙最強 ui 庫react-color
取色器react-draggle
拖拽dom-to-image
dom 節點轉成圖片
頁面 使用 React
+ Antd
方便快捷,三下五除二就搞定了
//每一行基本就是這樣子
const operationRow = ({ icon = "edit", label, component }) => (
<Row className={`${prefix}-item`}>
<Col span={labelSpan} className={`${prefix}-item-label`}>
<Button type="dashed" icon={icon}>
{label}
</Button>
</Col>
<Col
span={valueSpan}
offset={offsetSpan}
className={`${prefix}-item-input`}
>
{component}
</Col>
</Row>
);
複製程式碼
支援圖片拖拽
dragArea.addEventListener(
"dragleave",
e => {
this.stopAll(e);
this.removeDragAreaStyle();
},
false
);
//移動
dragArea.addEventListener(
"dragover",
e => {
this.stopAll(e);
this.addDragAreaStyle();
},
false
);
dragArea.addEventListener(
"drop",
e => {
this.stopAll(e);
this.removeDragAreaStyle();
const files = e.dataTransfer.files;
this.renderImage(Array.from(files)[0]);
},
false
);
複製程式碼
支援 圖片 貼上,這個也很簡單 繫結貼上事件 拿到 event 裡面的 data 渲染出來就行了
pasteHandler = e => {
const { items, types } = e.clipboardData;
if (!items) return;
const item = items[0]; //只要一張圖片
const { kind, type } = item; //kind 種類 ,type 型別
if (kind.toLocaleLowerCase() != "file") {
return message.error("錯誤的檔案型別!");
}
const file = item.getAsFile();
this.renderImage(file);
};
//貼上圖片
bindPasteListener = area => {
area.addEventListener("paste", this.pasteHandler);
};
複製程式碼
渲染圖片
renderImage = file => {
if (file && Object.is(typeof file, "object")) {
let { type, name, size } = file;
if (!isImage(type)) {
return message.error("無效的圖片格式");
}
this.setState({ loading: true });
const url = window.URL.createObjectURL(file);
this.setState({
currentImg: {
src: url,
size: `${~~(size / 1024)}KB`,
type
},
scale: defaultScale,
loading: false,
loadingImgReady: true
});
}
};
複製程式碼
其他就沒啥好說的了,常規的頁面佈局
4. 生成圖片
生成圖片 本質上就是 利用 canvas 的 ctx.drawImage()
,
繪製文字
const canvas = document.createElement(`cavans`)
const ctx = canvas.getContext(`2d`)
canvas.width = "預覽區域的寬"
canvas.height = "預覽區域的高"
//圖片
ctx.drawImage("URL",...attr)
//文字
ctx.fillText(TEXT,...attr)
//旋轉圖片
ctx.rotate(Math.PI/180 * 好多度)
//縮放 也是 呼叫 drawImage, 改變 sy,sx 繪製的其實就實現縮放了
ctx.drawImage(URL,0,0,sx /scale,sy/scale,0,0,x,y)
//轉成圖片
canvas.toDataURL(`image/png`,如果要壓縮這裡就填第二個引數)
複製程式碼
懂原理了 其實沒必要一行一行這樣寫了 找到個 現成的 dom-to-image
的庫,肥腸的不錯,也是開源和元件化得魅力啊,利人利己
呼叫 api domToimage.toPng()
輕鬆搞定
drawMeme = () => {
const { width, height, loadingImgReady,isCompress } = this.state;
if (!loadingImgReady) return message.error("請選擇圖片!");
this.setState({ drawLoading: true });
const imageArea = document.querySelector(".preview-content");
const options = {
width,
height,
}
if(isCompress){
options.quality = defaultQuality
}
domToImage
.toPng(imageArea, options)
.then(dataUrl => {
this.setState({ drawLoading: false });
Modal.confirm({
title: "生成成功",
content: <img src={dataUrl} style={{ maxWidth: "100%" }} />,
onOk: () => {
message.success("下載成功!");
const filename = Date.now()
const ext = isCompress ? `jpeg` : `png`
var link = document.createElement("a");
link.download = `${filename}.${ext}`;
link.href = dataUrl;
link.click();
},
okText: "立即下載",
cancelText: "再改一改"
});
})
.catch(err => {
message.error(err);
this.setState({ drawLoading: false });
});
};
複製程式碼
- MediaStream 實現 攝像頭捕捉
要想拿到 MediaStream
, 呼叫 navigatar.mediaDevices()
即可, 如果想研究 webRTC
,這些 API 也是基礎, 個人不是很感興趣,就沒研究,暫時只用到這個藉口
navigator.mediaDevices
.getUserMedia({
video: true,
audio: true
})
.then(stream => {
const cameraUrl = window.URL.createObjectURL(stream);
const hide = message.loading(`盛世美顏即將出現...`)
//其他程式碼
this.setState(
{
cameraUrl,
cameraVisible: true
},
() => {
setTimeout(()=>{
try {
this.video.play();
} catch (err) {
console.log(err);
Modal.error({
title: "攝像頭失敗",
content: err.message
});
} finally{
hide()
}
},1000)
}
);
})
.catch((err)=>{
console.log(err)
Modal.error({
title: "呼叫攝像頭失敗",
content: err.toString()
});
this.setState({ cameraVisible: false });
});
})
複製程式碼
這時頁面左上角 會彈出一個提示 問你是不是允許 使用攝像頭,同意後 拿到 stream
,否則進入 cath
使用 URL.createObjectURL()
拿到一個臨時的 url 連結
然後將 連結 設定成 <video src={臨時 URL}/>
呼叫 video.play()
//jsx
<video
style={{
display:"block",
margin:"0 auto"
}}
ref={video => (this.video = video)}
src={cameraUrl}
width={previewContentStyle.width}
height={previewContentStyle.height}
/>
複製程式碼
這時就會看見一個 帥氣的臉龐 出現在了螢幕上 !
這時來到了最後一步,擷取畫面,也很簡單 把 video 節點畫在 canvas 上, 然後 toDataURL()
蹬蹬,搞定
screenShotCamera = () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const { width, height } = previewContentStyle;
canvas.width = width;
canvas.height = height;
ctx.drawImage(this.video, 0, 0, width, height);
const data = canvas.toDataURL("image/png");
message.success(`擷取攝像頭畫面成功!`)
this.setState({
currentImg: {
src: data
},
cameraVisible:false,
scale: defaultScale,
loading: false,
loadingImgReady: true
});
};
複製程式碼
5. 下載圖片
下載圖片 基於 HTML5
的 download
屬性很好實現
const filename = Date.now()
const ext = isCompress ? `jpeg` : `png`
const link = document.createElement("a");
link.download = `${filename}.${ext}`;
link.href = dataUrl;
link.click();
複製程式碼
然後預設觸發一次點選事件 搞定
6. 結語
這樣一個支援 攝像頭
的 表情包製作器就完成了, 這時真的體會到了 npm
強大生態 和 元件化的 好處,
也學習到了 各種新 api 的使用! 老鐵沒毛病