Threejs(下面簡(jiǎn)稱 Three) 作為一個(gè) 3D 庫(kù),不僅減少了我們學(xué)習(xí) OpenGL 和 WebGL 的成本,還大大提升了前端在可視化上給用戶帶來更多的真實(shí)、沉浸式的體驗(yàn)。眾所周知,Three 更多的是用 3D 模型 + 投影相機(jī) + 用戶交互的方式來構(gòu)建一個(gè)「3D 世界」。
這張專輯,用眼睛去“聽” 活動(dòng)中,在視覺在只能提供「2D 切圖」的情況下,需要營(yíng)造「3D 效果」。為了獲得最好視覺體驗(yàn),僅僅通過貼圖很難做到,所以借此機(jī)會(huì)探索了 Three 的動(dòng)效方案。
運(yùn)動(dòng)往往是相對(duì)的,運(yùn)動(dòng)的本質(zhì)可能是「物體動(dòng)」或「相機(jī)動(dòng)」,本文將從對(duì)象動(dòng)畫和相機(jī)動(dòng)畫上闡述對(duì) Three 的動(dòng)效探索。
Three 提供多種相機(jī),其中應(yīng)用最廣的就是投影相機(jī) (PerspectiveCamera) ,通過投影相機(jī)可以模擬人眼所看見的效果。
const camera=THREE.PerspectiveCamera(fov, aspect, near, far);
復(fù)制代碼
參數(shù) 含義 默認(rèn)值 fov fov 是視景體豎直方向上(非水平!)的張角,人類有接近180度的視角大小。該值可根據(jù)具體場(chǎng)景所需要的視角設(shè)置。 45 aspect 指定渲染結(jié)果的橫向尺寸和縱向尺寸的比值。該值通常設(shè)置為窗口大小的寬高比。 window.innerWidth / window.innerHeight near 表示可以看到多近的物體。這個(gè)值通常很小。 0.1 far 表示可以看到多遠(yuǎn)的物體。這個(gè)看情況設(shè)置,過大會(huì)導(dǎo)致渲染過多;太小可能又會(huì)看不到。 1000
ps: 在 Three 中是沒有「長(zhǎng)度單位」這個(gè)概念的,它的數(shù)值都是根據(jù)比例計(jì)算得出,因此這里提到的 0.1 或 1000 都沒有具體的含義,而是一種相對(duì)長(zhǎng)度。
可以看到,通過配置透視相機(jī)的相關(guān)參數(shù),最終被渲染到屏幕上的,是在 near 到 far 之間,根據(jù) fov 的值和物體遠(yuǎn)近 d 確定渲染高度,再通過 aspect 值來確定渲染寬度的。
有了相機(jī),我們還要有場(chǎng)景,場(chǎng)景是為了讓我們?cè)O(shè)置我們的空間內(nèi)「有什么」和「放在哪」的。我們可以在場(chǎng)景中放置物體,光源還有相機(jī)。
const scene=new THREE.Scene();
復(fù)制代碼
是的,創(chuàng)建場(chǎng)景就是這么簡(jiǎn)單。
為了以群的維度去區(qū)分場(chǎng)景中的物體,我們還可以在場(chǎng)景中添加 Group。有了 Group,可以更方便地操作一類物體。 比如創(chuàng)建一個(gè) stoneGroup,并添加到場(chǎng)景中:
const stoneGroup=new THREE.Group();
stoneGroup.name='stoneGroup';
scene.add(stoneGroup);
復(fù)制代碼
為 Group 命名,允許我們通過 name 來獲取到對(duì)應(yīng)的 Group:
const group=scene.getObjectByName(name);
復(fù)制代碼
Three 提供了多種類型的幾何體,可以分為二維網(wǎng)格和三維網(wǎng)格。二維網(wǎng)格顧名思義只有兩個(gè)維度,可以通過這種幾何體創(chuàng)建簡(jiǎn)單的二維平面;三維網(wǎng)格允許你定義三維物體;在 Three 中定義一個(gè)幾何體十分簡(jiǎn)單,只需要選擇需要的幾何體并傳入相應(yīng)參數(shù)創(chuàng)建即可。
查看Three提供的幾何體
如果看到 Three 提供的幾何體,可以看到有的幾何體中它分別提供 Geometery 和 BufferGeometery 版本,關(guān)于這兩個(gè)的區(qū)別,可以看這里 回答
大致意思就是使用 Buffer 版本的幾何體相較于普通的幾何體會(huì)將描述物體的數(shù)據(jù)存放在緩沖區(qū)中,減少內(nèi)存消耗和 CPU 循環(huán)。通過它們提供的方法來看,使用 geometry 無疑是對(duì)新手友好的。
創(chuàng)建幾何體:
// 創(chuàng)建立方體,傳入長(zhǎng)、寬和高
var cubeGeometry=new THREE.CubeGeometry(40, 40, 40);
// 創(chuàng)建球體,傳入半徑、寬片段數(shù)量和高片段數(shù)量
var sphereGeometry=new THREE.SphereGeometry(20, 100, 100);
復(fù)制代碼
定義材質(zhì)可以幫助我們決定一個(gè)物體在各種環(huán)境情況下的具體表現(xiàn)。同樣 Three 也提供了多種材質(zhì)。下面列舉幾個(gè)常用的材質(zhì)。
名稱 描述 MeshBasicMaterial 基礎(chǔ)材質(zhì),用它定義幾何體上的簡(jiǎn)單顏色或線框 MeshPhongMaterial 受光照影響,用來創(chuàng)建光亮的物體 MeshLambertMaterial 受光照影響,用來創(chuàng)建不光亮的物體 MeshDepthMaterial 根據(jù)相機(jī)遠(yuǎn)近來決定如何給網(wǎng)格染色
創(chuàng)建材質(zhì):
var basicMaterial=new THREE.MeshBasicMaterial({ color: 0x666666 });
var lambertMaterial=new THREE.MeshLambertMaterial({ color: 0x666666 });
var phongMaterial=new THREE.MeshPhongMaterial({ color: 0x666666 });
var wireMaterial=new THREE.MeshBasicMaterial({ wireframe: true, color: 0x666666 });
復(fù)制代碼
更多材質(zhì)和相關(guān)信息,可以查看 材質(zhì)
需要添加到場(chǎng)景中,還需要依賴 Mesh。Mesh 是用來定義材質(zhì)和幾何體之間是如何粘合的,創(chuàng)建網(wǎng)格對(duì)象可以應(yīng)用一個(gè)或多個(gè)材質(zhì)和幾何體。
創(chuàng)建幾何體相同材質(zhì)不同的網(wǎng)格對(duì)象:
var cube=new THREE.Mesh(cubeGeometry, basicMaterial);
var cubePhong=new THREE.Mesh(cubeGeometry, phongMaterial);
scene.add(cube, cubePhong);
復(fù)制代碼
創(chuàng)建材質(zhì)相同幾何體不同的網(wǎng)格對(duì)象:
var cube=new THREE.Mesh(cubeGeometry, basicMaterial);
var sphere=new THREE.Mesh(sphereGeometry, basicMaterial);
scene.add(cube, sphere);
復(fù)制代碼
創(chuàng)建擁有多個(gè)材質(zhì)幾何體的網(wǎng)格對(duì)象:
var phongMaterial=new THREE.MeshPhongMaterial({ color: 0x666666 });
var cubeMeshPhong=new THREE.Mesh(cubeGeometry, cubePhongMaterial);
var cubeMeshWire=new THREE.Mesh(cubeGeometry, wireMaterial);
// 網(wǎng)格對(duì)象新增材質(zhì)
cubeMeshPhong.add(cubeMeshWire);
scene.add(cubeMeshPhong);
復(fù)制代碼
有了場(chǎng)景和相機(jī),我們還需要渲染器把對(duì)應(yīng)的場(chǎng)景用對(duì)應(yīng)的相機(jī)可見渲染出來,因此渲染器需要傳入場(chǎng)景和相機(jī)參數(shù)。
// 抗鋸齒、canvas 是否支持 alpha 透明度、preserveDrawingBuffer 是否保存 BUFFER 直到手動(dòng)清除
const renderer=new THREE.WebGLRenderer({
antialias: true, alpha: true, preserveDrawingBuffer: true
});
renderer.setSize(this.width, this.height);
renderer.autoClear=true;
// 清除顏色,第二個(gè)參數(shù)為 0 表示完全透明,適用于需要透出背景的場(chǎng)景
renderer.setClearColor(0x000000, 0);
renderer.setPixelRatio(window.devicePixelRatio);
復(fù)制代碼
為了在相機(jī)更新后所看見的場(chǎng)景,需要在循環(huán)渲染中加上
renderer.render(scene, camera);
復(fù)制代碼
有了相機(jī)場(chǎng)景和渲染器,我們已經(jīng)可以看到初步的效果了。但3D世界里,靜止的物體多無趣啊。于是我們嘗試加入動(dòng)畫效果。
Three為動(dòng)畫提供了一系列方法。
參數(shù) 含義 AnimationMixer 作為特定對(duì)象的動(dòng)畫混合器,可以管理該對(duì)象的所有動(dòng)畫 AnimationAction 為播放器指定對(duì)應(yīng)的片段存儲(chǔ)一系列行為,用來指定動(dòng)畫快慢,循環(huán)類型等 AnimationClip 表示可重用的動(dòng)畫行為片段,用來指定一個(gè)動(dòng)畫的動(dòng)畫效果(放大縮小、上下移動(dòng)等) KeyframeTrack 與時(shí)間相關(guān)的幀序列,傳入時(shí)間和值,應(yīng)用在指定對(duì)象的屬性上。目前有 BooleanKeyframeTrack VectorKeyframeTrack 等。
那么如何創(chuàng)建一個(gè)動(dòng)畫呢?下面這個(gè)例子給大家解釋如何讓網(wǎng)格對(duì)象進(jìn)行簡(jiǎn)單的上下移動(dòng)。
創(chuàng)建特定對(duì)象的動(dòng)畫混合器:
// 創(chuàng)建紋理
const texture=new THREE.TextureLoader().load(img.src);
// 使用紋理創(chuàng)建貼圖
const material=new THREE.SpriteMaterial({ map: texture, color: 0x666666 });
// 使用貼圖創(chuàng)建貼圖對(duì)象
const stone=new THREE.Sprite(material);
// 為貼圖對(duì)象創(chuàng)建動(dòng)畫混合器
const mixer=new THREE.AnimationMixer(stone);
復(fù)制代碼
創(chuàng)建動(dòng)畫行為片段:
const getClip=(pos=[0, 0, 0])=> {
const [x, y, z]=pos;
const times=[0, 1]; // 關(guān)鍵幀時(shí)間數(shù)組,離散的時(shí)間點(diǎn)序列
const values=[x, y, z, x, y + 3, z]; // 與時(shí)間點(diǎn)對(duì)應(yīng)的值組成的數(shù)組
// 創(chuàng)建位置關(guān)鍵幀對(duì)象:0時(shí)刻對(duì)應(yīng)位置0, 0, 0 10時(shí)刻對(duì)應(yīng)位置150, 0, 0
const posTrack=new THREE.VectorKeyframeTrack('stone.position', times, values);
const duration=1;
return new THREE.AnimationClip('stonePosClip', duration, [posTrack]);
};
復(fù)制代碼
創(chuàng)建動(dòng)畫播放器,確定動(dòng)畫的表現(xiàn):
const action=mixer.clipAction(getClip([x, y, z]));
action.timeScale=1; // 動(dòng)畫播放一個(gè)周期的時(shí)間
action.loop=THREE.LoopPingPong; // 動(dòng)畫循環(huán)類型
action.play(); // 播放
復(fù)制代碼
在循環(huán)繪制中更新混合器,保證動(dòng)畫的執(zhí)行:
animate() {
// 更新動(dòng)畫
const delta=this.clock.getDelta();
mixer.update(delta);
requestAnimationFrame(()=> {
animate();
});
}
復(fù)制代碼
codepen
有了 Animation 我們可以很簡(jiǎn)單地對(duì)物體的一些屬性進(jìn)行操作。但一些貼圖相關(guān)的動(dòng)畫就很難用 Animation 來實(shí)現(xiàn)了,比如:
上圖這種,無法通過改變物體的位置、大小等屬性實(shí)現(xiàn)。于是,還有一種方案 —— 貼圖動(dòng)畫。
類似在 CSS3 中對(duì)序列圖片使用 transform 屬性改變位置來達(dá)到的動(dòng)畫效果,實(shí)際上在 Three 中也可以使用貼圖位移的方式實(shí)現(xiàn)。
首先,我們要有一個(gè)序列圖:
作為紋理加載,并且增加到場(chǎng)景中:
const arrowTexture=new THREE.TextureLoader().load(Arrow);
const material=new THREE.SpriteMaterial({ map: arrowTexture, color: 0xffffff });
const arrow=new THREE.Sprite(material);
scene.add(arrow);
復(fù)制代碼
聲明 TextAnimator 對(duì)象,實(shí)現(xiàn)紋理的位移:
function TextureAnimator(texture, tilesHoriz, tilesVert, numTiles, tileDispDuration) {
// 紋理對(duì)象通過引用傳入,之后可以直接使用update方法更新紋理位置
this.tilesHorizontal=tilesHoriz;
this.tilesVertical=tilesVert;
// 序列圖中的幀數(shù)
this.numberOfTiles=numTiles;
texture.wrapS=THREE.RepeatWrapping;
texture.wrapT=THREE.RepeatWrapping;
texture.repeat.set(1 / this.tilesHorizontal, 1 / this.tilesVertical);
// 每一幀停留時(shí)長(zhǎng)
this.tileDisplayDuration=tileDispDuration;
// 當(dāng)前幀停留時(shí)長(zhǎng)
this.currentDisplayTime=0;
// 當(dāng)前幀
this.currentTile=0;
// 更新函數(shù),通過這個(gè)函數(shù)對(duì)紋理位移進(jìn)行更新
this.update=(milliSec)=> {
this.currentDisplayTime +=milliSec;
while (this.currentDisplayTime > this.tileDisplayDuration) {
this.currentDisplayTime -=this.tileDisplayDuration;
this.currentTile++;
if (this.currentTile===this.numberOfTiles) { this.currentTile=0; }
const currentColumn=this.currentTile % this.tilesHorizontal;
texture.offset.x=currentColumn / this.tilesHorizontal;
const currentRow=Math.floor(this.currentTile / this.tilesHorizontal);
texture.offset.y=currentRow / this.tilesVertical;
}
};
}
復(fù)制代碼
// 傳入一個(gè)一行里有 13 幀的序列圖,每張序列圖停留 75ms
const arrowAni=new TextureAnimator(arrowTexture, 13, 1, 13, 75);
復(fù)制代碼
在循環(huán)繪制中更新,保證動(dòng)畫的執(zhí)行:
arrowAni.update(delta);
復(fù)制代碼
作為引用傳入后,對(duì)貼圖的修改會(huì)直接體現(xiàn)在使用該貼圖的材質(zhì)上。
codepen
Three 中還提供了酷炫的粒子動(dòng)畫,使用繼承自 Object3D 的 Points 類實(shí)現(xiàn)。有了 Points 類我們可以很方便地把一個(gè)幾何體渲染成一組粒子,并對(duì)它們進(jìn)行控制。
創(chuàng)建粒子我們首先需要?jiǎng)?chuàng)建粒子的材質(zhì),可以使用 PointsMaterial 創(chuàng)建粒子材質(zhì)。
const texture=new THREE.TextureLoader().load('https://p1.music.126.net/jgzbZtWZhDet2jWzED8BTw==/109951164579600342.png');
material=new THREE.PointsMaterial({
color: 0xffffff,
// 映射到材質(zhì)上的貼圖
map: texture,
size: 2,
// 粒子的大小是否和其與攝像機(jī)的距離有關(guān),默認(rèn)值 true
sizeAttenuation: true,
});
// 開啟透明度測(cè)試,透明度低于0.5的片段會(huì)被丟棄,解決貼圖邊緣感問題
material.alphaTest=0.5;
復(fù)制代碼
有了粒子材質(zhì)后,我們可以應(yīng)用同一個(gè)材質(zhì)批量創(chuàng)建一組粒子,只需要傳入一個(gè)簡(jiǎn)單的幾何體。
var particles=new THREE.Points( geometry, material );
復(fù)制代碼
如果你傳入的是 BoxGeometry 你可能會(huì)得到這樣的一組粒子
還可以根據(jù)傳入的 Shape 得到這樣一組粒子
但有趣的粒子絕不是靜止的,而是有活動(dòng)、有過程的。但如果自己動(dòng)手實(shí)現(xiàn)一個(gè)粒子的運(yùn)動(dòng)又很復(fù)雜,因此希望借助一些第三方庫(kù)實(shí)現(xiàn)粒子動(dòng)畫的緩動(dòng)過程。
tween.js 是一個(gè)小型的 JS 庫(kù),我們可以使用它為我們的動(dòng)畫聲明變化。使用 tween.js 我們不需要關(guān)心運(yùn)動(dòng)的中間狀態(tài),只需要關(guān)注粒子的:
// srcPosition, targetPosition;
tweens.push(new TWEEN.Tween(srcPosition).easing(TWEEN.Easing.Exponential.In));
// tweens最終位置、緩動(dòng)時(shí)間
tweens[0].to(targetPosition, 5000);
tweens[0].start();、
復(fù)制代碼
codepen
其實(shí)粒子動(dòng)畫的場(chǎng)景還有很多,我們可以用他們創(chuàng)造雪花飄散、穿梭效果,本質(zhì)都是粒子的位置變化。
相機(jī)在 3D 空間中充當(dāng)人的眼睛,因此自然的相機(jī)動(dòng)線可以保證交互的自然流暢。
Three 提供了一系列相機(jī)控件來控制場(chǎng)景中的相機(jī)軌跡,這些控件適用于大部分場(chǎng)景。使用 Controls 開發(fā)者可以不再需要去關(guān)心用戶交互和相機(jī)移動(dòng)的問題。
活動(dòng)中也涉及到 OrbitControls 的使用,他提供了環(huán)繞物體旋轉(zhuǎn)、平移和縮放的方法,但由于對(duì)使用二維貼圖的情況下,旋轉(zhuǎn)和縮放都容易穿幫,需要被禁止。
// 創(chuàng)建軌跡
const controls=new THREE.OrbitControls(this.camera, this.renderer.domElement);
controls.enabled=!0;
controls.target=new THREE.Vector3();
controls.minDistance=0;
controls.maxDistance=2000;
controls.minPolarAngle=Math.PI / 2;
controls.maxPolarAngle=Math.PI / 2;
// 禁用縮放
controls.enableZoom=!1;
// 禁用旋轉(zhuǎn)
controls.enableRotate !1;
controls.panSpeed=2;
// 修改控件的默認(rèn)觸摸選項(xiàng),設(shè)置為單指雙指都為平移操作
controls.touches={
ONE: THREE.TOUCH.PAN,
TWO: THREE.TOUCH.PAN,
};
this.scene.add(this.camera);
復(fù)制代碼
OrbitControl 還允許我們?cè)O(shè)置阻尼,設(shè)置該值表現(xiàn)為數(shù)值越接近 1 越難拖動(dòng),開啟阻尼后需要我們手動(dòng) update 控件。
controls.enableDamping=!0;
controls.dampingFactor=0.2;
復(fù)制代碼
查看源碼可以看到,阻尼的實(shí)現(xiàn)就是依賴滑動(dòng)時(shí)的 offset 乘上一個(gè)權(quán)重,在通過后續(xù)的update不斷為 panOffset 乘上一個(gè)權(quán)重實(shí)現(xiàn)滑動(dòng)難,撒手后再滑動(dòng)一點(diǎn)距離。
// this method is exposed, but perhaps it would be better if we can make it private...
this.update=function () {
// ...
return function update() {
// ...
// 平移
if ( scope.enableDamping===true ) {
// 開啟阻尼后會(huì)在原本的位移上乘上一個(gè)權(quán)重
scope.target.addScaledVector( panOffset, scope.dampingFactor );
} else {
scope.target.add( panOffset );
}
// ...
if ( scope.enableDamping===true ) {
sphericalDelta.theta *=( 1 - scope.dampingFactor );
sphericalDelta.phi *=( 1 - scope.dampingFactor );
// 如果沒有人為操作,隨著時(shí)間推移,panOffset會(huì)越來越小
panOffset.multiplyScalar( 1 - scope.dampingFactor );
} else {
sphericalDelta.set( 0, 0, 0 );
panOffset.set( 0, 0, 0 );
}
// ...
};
}();
復(fù)制代碼
官方也提供了 Controls 的 例子 供大家參考。
如果不使用 Controls,僅僅是相機(jī)從一個(gè)點(diǎn)移動(dòng)到另一個(gè)點(diǎn),為了更平滑自然的相機(jī)軌跡,推薦使用貝塞爾曲線。
貝塞爾曲線是一個(gè)由起點(diǎn)、終點(diǎn)和控制點(diǎn)決定的一條時(shí)間相關(guān)的變化曲線。這里以二階貝塞爾曲線為例,實(shí)現(xiàn)相機(jī)的曲線移動(dòng)。(三維的點(diǎn)有點(diǎn)難說明白,這里用二維坐標(biāo)來解釋)
上圖中小黑點(diǎn)的移動(dòng)軌跡可以看做相機(jī)移動(dòng)的曲線。
從該公式來看,只需要確定 p0、p1 和 p2 三個(gè)點(diǎn),在單位時(shí)間下我們可以獲得一條確定的曲線。
但是,換成坐標(biāo)點(diǎn)要怎么做呢?
// 獲得貝塞爾曲線
function getBezier(p1, p2) {
// 在指定范圍內(nèi)隨機(jī)生成一個(gè)控制點(diǎn)
const cp={
x: p1.x + Math.random() * 100 + 200,
z: p2.z + Math.random() * 200,
};
let t=0;
// 貝塞爾曲線公式,根據(jù)時(shí)間確定點(diǎn)的位置
return (deltat)=> {
if (t >=1) return [p2.x, p2.y];
t +=deltat;
if (t > 1) t=1;
const { x: x1, z: z1 }=p1;
const { x: cx, z: cz }=cp;
const { x: x2, z: z2 }=p2;
const x=(1 - t) * (1 - t) * x1 + 2 * t * (1 - t) * cx + t * t * x2;
const z=(1 - t) * (1 - t) * z1
+ 2 * t * (1 - t) * cz + t * t * z2;
return [x, z];
};
}
復(fù)制代碼
const bezier=getBezier(p1, p2);
復(fù)制代碼
為了從簡(jiǎn),這里只實(shí)現(xiàn)了二維坐標(biāo)的軌跡變化,但三維也是同理。
因?yàn)樨惾麪柷€是時(shí)間相關(guān)曲線,在每一次循環(huán)渲染中要傳入時(shí)間來更新相機(jī)位置。
animation() {
const [x, z]=bezier(clock.getDelta());
camera.position.x=x;
camera.position.z=z;
requestAnimationFrame(()=> {
animate();
});
}
復(fù)制代碼
沒趕上 Three 的熱潮,只能趁著活動(dòng)需求給自己補(bǔ)補(bǔ)課了。在三維空間中,動(dòng)畫能夠讓空間中的物體更加生動(dòng),而相機(jī)的移動(dòng)帶給用戶更強(qiáng)的空間感。
本文介紹了基于 Animation 實(shí)現(xiàn)物體的簡(jiǎn)單運(yùn)動(dòng)、 Texture 實(shí)現(xiàn)貼圖動(dòng)畫以及使用 Points 粒子化的物體動(dòng)畫方案;基于 Controls 和貝塞爾曲線的相機(jī)動(dòng)畫方案。
對(duì) Three 有興趣的朋友,可以通過 官方文檔 來學(xué)習(xí),里面提供的例子覆蓋了大部分場(chǎng)景。
以上是我在活動(dòng)中涉及到的一些動(dòng)畫方案,難免會(huì)出現(xiàn)理解偏差和表達(dá)錯(cuò)誤,如果有更多的動(dòng)效方案歡迎一起探討~
家好,很高興又見面了,我是"高級(jí)前端?進(jìn)階?",由我?guī)е蠹乙黄痍P(guān)注前端前沿、深入前端底層技術(shù),大家一起進(jìn)步,也歡迎大家關(guān)注、點(diǎn)贊、收藏、轉(zhuǎn)發(fā),您的支持是我不斷創(chuàng)作的動(dòng)力。
今天給大家?guī)淼闹黝}是HTML5 和word的互相轉(zhuǎn)化,話不多說,直接進(jìn)入正題!
html-docx-js 是一個(gè)非常小的庫(kù),能夠?qū)?HTML 文檔轉(zhuǎn)換為 Microsoft Word 2007 及更高版本使用的 DOCX 格式。 html-docx-js 設(shè)法使用稱為“altchunks”的功能在瀏覽器中執(zhí)行轉(zhuǎn)換。 簡(jiǎn)而言之,它允許以不同的標(biāo)記語言嵌入內(nèi)容。 開發(fā)者使用 MHT 文檔將嵌入內(nèi)容發(fā)送到 Word,因?yàn)樗试S處理圖像。 Word 打開此類文件后,會(huì)將外部?jī)?nèi)容轉(zhuǎn)換為 Word Processing ML(這是 DOCX 文件的標(biāo)記語言的調(diào)用方式)并替換引用。
Microsoft Word for Mac 2008 不支持 Altchunk,LibreOffice 和 Google Docs 也不支持 Altchunk。
關(guān)于 html-docx-js 庫(kù)有幾點(diǎn)需要說明:
目前 Mammoth 在 Github 上通過 MIT 協(xié)議開源,有超過 1k 的 star、0.3k 的 fork、0.7k 的項(xiàng)目依賴量、NPM 周平均下載量 9k,是一個(gè)值得關(guān)注的前端開源項(xiàng)目。
var converted=htmlDocx.asBlob(content);
saveAs(converted, "test.docx");
asBlob 可以采用其他選項(xiàng)來控制文檔的頁(yè)面設(shè)置:
比如下面的例子:
var converted=htmlDocx.asBlob(content, {
orientation: "landscape",
margins: { top: 720 },
});
saveAs(converted, "test.docx");
需要注意的是,開發(fā)者需要傳遞完整、有效的 HTML(包括 DOCTYPE、html 和 body 標(biāo)簽)。 這可能不太方便,但可以讓開發(fā)者在樣式標(biāo)簽中包含 CSS 規(guī)則。
html-docx-js 作為獨(dú)立”Browserify 模塊(UMD)分發(fā)。 開發(fā)者可以將其作為 html-docx 要求。 如果沒有可用的模塊加載器,它將把自己注冊(cè)在 window.htmlDocx。
Mammoth.js 旨在轉(zhuǎn)換 .docx 文檔,例如:由 Microsoft Word、Google Docs 和 LibreOffice 創(chuàng)建的文檔,并將其轉(zhuǎn)換為 HTML。 Mammoth 的目標(biāo)是通過使用文檔中的語義信息并忽略其他細(xì)節(jié)來生成簡(jiǎn)單且干凈的 HTML。 例如,Mammoth 將任何具有標(biāo)題 1 樣式的段落轉(zhuǎn)換為 h1 元素,而不是嘗試精確復(fù)制標(biāo)題的樣式(字體、文本大小、顏色等)。
.docx 使用的結(jié)構(gòu)與 HTML 的結(jié)構(gòu)之間存在很大的不匹配,這意味著對(duì)于更復(fù)雜的文檔來說,轉(zhuǎn)換不太可能完美。 如果開發(fā)者僅使用樣式來對(duì)文檔進(jìn)行語義標(biāo)記,那么 Mammoth 效果最佳。
Mammoth.js 目前支持以下功能:
Mammoth 在眾多平臺(tái)可用,比如:Python、WordPress、Java/JVM、.NET 等等。目前 Mammoth 在 Github 上通過 BSD-2-Clause 開源,有超過 4.1k 的 star、0.5k 的 fork、4.4k 的項(xiàng)目依賴量、NPM 周平均下載量 76k,是一個(gè)值得關(guān)注的前端優(yōu)質(zhì)開源項(xiàng)目。
以文檔轉(zhuǎn)換為例。
Mammoth 允許在轉(zhuǎn)換文檔之前對(duì)其進(jìn)行處理。 例如,假設(shè)該文檔尚未進(jìn)行語義標(biāo)記,但開發(fā)者知道任何居中對(duì)齊的段落都應(yīng)該是標(biāo)題,則可以使用 transformDocument 參數(shù)來適當(dāng)?shù)匦薷奈臋n:
function transformElement(element) {
if (element.children) {
var children=_.map(element.children, transformElement);
element={ ...element, children: children };
}
if (element.type==="paragraph") {
element=transformParagraph(element);
}
return element;
}
function transformParagraph(element) {
if (element.alignment==="center" && !element.styleId) {
return { ...element, styleId: "Heading2" };
} else {
return element;
}
}
var options={
transformDocument: transformElement,
};
TransformDocument 的返回值在 HTML 生成期間使用。同時(shí),上面的代碼可以使用 mammoth.transforms.paragraph 函數(shù)進(jìn)行優(yōu)化,比如:
function transformParagraph(element) {
if (element.alignment==="center" && !element.styleId) {
return { ...element, styleId: "Heading2" };
} else {
return element;
}
}
var options={
transformDocument: mammoth.transforms.paragraph(transformParagraph),
};
或者,如果開發(fā)者希望已明確設(shè)置為使用等寬字體來表示代碼的段落:
const monospaceFonts=["consolas", "courier", "courier new"];
function transformParagraph(paragraph) {
var runs=mammoth.transforms.getDescendantsOfType(paragraph, "run");
var isMatch=runs.length > 0 &&
runs.every(function (run) {
return run.font && monospaceFonts.indexOf(run.font.toLowerCase()) !==-1;
});
if (isMatch) {
return {
...paragraph,
styleId: "code",
styleName: "Code",
};
} else {
return paragraph;
}
}
var options={
transformDocument: mammoth.transforms.paragraph(transformParagraph),
styleMap: ["p[style-name='Code']=> pre:separator('\n')"],
};
關(guān)于 Mammoth 庫(kù)的更多用法,更多 API 示例可以參考文末資料,本文不再過多展開。
本文主要和大家介紹 HTML5 和word互轉(zhuǎn)的兩個(gè)開源庫(kù),分別為:html-docx-js、mammoth.js。相信通過本文的閱讀,大家對(duì) html-docx-js、mammoth.js 會(huì)有一個(gè)初步的了解。
因?yàn)槠邢蓿P(guān)于 html-docx-js、mammoth.js 的更多用法和特性文章并沒有過多展開,如果有興趣,可以在我的主頁(yè)繼續(xù)閱讀,同時(shí)文末的參考資料提供了大量?jī)?yōu)秀文檔以供學(xué)習(xí)。最后,歡迎大家點(diǎn)贊、評(píng)論、轉(zhuǎn)發(fā)、收藏,您的支持是我不斷創(chuàng)作的動(dòng)力。
https://github.com/evidenceprime/html-docx-js
https://github.com/mwilliamson/mammoth.js
https://www.npmjs.com/package/html-docx-js
https://www.npmjs.com/package/mammoth
https://www.tutorialswebsite.com/export-html-to-word-document-with-javascript/
https://www.vecteezy.com/vector-art/136754-free-vector-documents
擊右上方紅色按鈕關(guān)注“web秀”,讓你真正秀起來
在css3到來之前,都是用js來操作dom元素,計(jì)算位置,大小,形成瀑布流布局。但是有了css3之后,一切實(shí)現(xiàn)起來就太簡(jiǎn)單了,沒有復(fù)雜的邏輯,輕松的幾行樣式代碼就可以搞定。
基于waterfall.js(11.8kb),還得寫入基礎(chǔ)的樣式,初始化等等,對(duì)比其他js,已經(jīng)是很簡(jiǎn)單了。
var waterfall=new WaterFall({ container: '#waterfall', pins: ".pin", loader: '#loader', gapHeight: 20, gapWidth: 20, pinWidth: 216, threshold: 100 });
但是,有了css3,再簡(jiǎn)潔的js,都比不過簡(jiǎn)單的css代碼。
關(guān)鍵點(diǎn),橫向 flex 布局嵌套多列縱向 flex 布局,使用了 vw 進(jìn)行自適應(yīng)縮放
// pug 模板引擎 div.g-container -for(var i=0; i<4; i++) div.g-queue -for(var j=0; j<8; j++) div.g-item
不熟悉pug模板(jade)的,可以先去了解一下。其實(shí)看大致也就懂了,就是循環(huán)多個(gè)元素,沒有復(fù)雜的邏輯。
$lineCount: 4; $count: 8; // 隨機(jī)數(shù)(瀑布流某塊的高度) @function randomNum($max, $min: 0, $u: 1) { @return ($min + random($max)) * $u; } // 隨機(jī)顏色值 @function randomColor() { @return rgb(randomNum(255), randomNum(255), randomNum(255)); } .g-container { display: flex; flex-direction: row; justify-content: space-between; overflow: hidden; } .g-queue { display: flex; flex-direction: column; flex-basis: 24%; } .g-item { position: relative; width: 100%; margin: 2.5% 0; } @for $i from 1 to $lineCount+1 { .g-queue:nth-child(#{$i}) { @for $j from 1 to $count+1 { .g-item:nth-child(#{$j}) { height: #{randomNum(300, 50)}px; background: randomColor(); // 瀑布流某塊中間的數(shù)字 &::after { content: "#{$j}"; position: absolute; color: #fff; font-size: 24px; // 水平垂直居中 top: 50%; left: 50%; transform: translate(-50%, -50%); } } } } }
預(yù)覽:
CSS 實(shí)現(xiàn)瀑布流布局(display: flex)
演示地址: 點(diǎn)擊文章結(jié)尾“了解更多”
關(guān)鍵點(diǎn), column-count: 元素內(nèi)容將被劃分的最佳列數(shù) break-inside: 避免在元素內(nèi)部插入分頁(yè)符
// pug 模板引擎 div.g-container -for(var j=0; j<32; j++) div.g-item
column-count 看起來比display: flex更科學(xué),模板不需要2層循環(huán),更簡(jiǎn)潔明了。如果真正用到數(shù)據(jù)上面,會(huì)更好處理。
$count: 32; // 隨機(jī)數(shù)(瀑布流某塊的高度) @function randomNum($max, $min: 0, $u: 1) { @return ($min + random($max)) * $u; } // 隨機(jī)顏色值 @function randomColor() { @return rgb(randomNum(255), randomNum(255), randomNum(255)); } .g-container { column-count: 4; column-gap: .5vw; padding-top: .5vw; } .g-item { position: relative; width: 24vw; margin-bottom: 1vw; break-inside: avoid; } @for $i from 1 to $count+1 { .g-item:nth-child(#{$i}) { height: #{randomNum(300, 50)}px; background: randomColor(); &::after { content: "#{$i}"; position: absolute; color: #fff; font-size: 2vw; top: 50%; left: 50%; transform: translate(-50%, -50%); } } }
預(yù)覽:
CSS 實(shí)現(xiàn)瀑布流布局(column-count)
演示地址: 點(diǎn)擊文章結(jié)尾“了解更多”
關(guān)鍵點(diǎn), 使用 grid-template-columns、grid-template-rows 分割行列 使用 grid-row 控制每個(gè) item 的所占格子的大小
// pug 模板引擎 div.g-container -for(var i=0; i<8; i++) div.g-item
樣式
$count: 8; // 隨機(jī)數(shù)(瀑布流某塊的高度) @function randomNum($max, $min: 0, $u: 1) { @return ($min + random($max)) * $u; } // 隨機(jī)顏色值 @function randomColor() { @return rgb(randomNum(255), randomNum(255), randomNum(255)); } .g-container { height: 100vh; display: grid; grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(8, 1fr); } @for $i from 1 to $count+1 { .g-item:nth-child(#{$i}) { position: relative; background: randomColor(); margin: 0.5vw; &::after { content: "#{$i}"; position: absolute; color: #fff; font-size: 2vw; top: 50%; left: 50%; transform: translate(-50%, -50%); } } } .g-item { &:nth-child(1) { grid-column: 1; grid-row: 1 / 3; } &:nth-child(2) { grid-column: 2; grid-row: 1 / 4; } &:nth-child(3) { grid-column: 3; grid-row: 1 / 5; } &:nth-child(4) { grid-column: 4; grid-row: 1 / 6; } &:nth-child(5) { grid-column: 1; grid-row: 3 / 9; } &:nth-child(6) { grid-column: 2; grid-row: 4 / 9; } &:nth-child(7) { grid-column: 3; grid-row: 5 / 9; } &:nth-child(8) { grid-column: 4; grid-row: 6 / 9; } }
display: grid樣式上面感覺也不好用,需要書寫很多grid-column、grid-row。
預(yù)覽:
CSS 實(shí)現(xiàn)瀑布流布局(display: grid)
演示地址: 點(diǎn)擊文章結(jié)尾“了解更多”
通過,這3種CSS瀑布流布局,你更喜歡哪一種呢?
個(gè)人更喜歡column-count,看起來更加清晰,容易理解,代碼量也很少。
喜歡小編的點(diǎn)擊關(guān)注,了解更多知識(shí)!
源碼地址請(qǐng)點(diǎn)擊下方“了解更多”
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。