整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          Three.js 動(dòng)效方案

          Three.js 動(dòng)效方案



          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 基礎(chǔ)

          Camera 相機(jī)

          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 值來確定渲染寬度的。

          Scene 場(chǎng)景

          有了相機(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)單。

          Group

          為了以群的維度去區(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ù)制代碼

          Geometry 幾何體

          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ù)制代碼

          Material 材質(zhì)

          定義材質(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ì)

          Mesh網(wǎng)格對(duì)象

          需要添加到場(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ù)制代碼

          Renderer 渲染器

          有了場(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)畫效果。

          物體動(dòng)畫

          Animations

          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

          貼圖動(dòng)畫

          有了 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

          粒子動(dòng)畫

          Three 中還提供了酷炫的粒子動(dòng)畫,使用繼承自 Object3D 的 Points 類實(shí)現(xiàn)。有了 Points 類我們可以很方便地把一個(gè)幾何體渲染成一組粒子,并對(duì)它們進(jìn)行控制。

          創(chuàng)建粒子

          創(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 得到這樣一組粒子



          粒子運(yùn)動(dòng)

          但有趣的粒子絕不是靜止的,而是有活動(dòng)、有過程的。但如果自己動(dòng)手實(shí)現(xiàn)一個(gè)粒子的運(yùn)動(dòng)又很復(fù)雜,因此希望借助一些第三方庫(kù)實(shí)現(xiàn)粒子動(dòng)畫的緩動(dòng)過程。

          tween.js

          tween.js 是一個(gè)小型的 JS 庫(kù),我們可以使用它為我們的動(dòng)畫聲明變化。使用 tween.js 我們不需要關(guān)心運(yùn)動(dòng)的中間狀態(tài),只需要關(guān)注粒子的:

          • 起始位置
          • 最終位置
          • 緩動(dòng)效果
          // 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ī)動(dòng)畫

          相機(jī)在 3D 空間中充當(dāng)人的眼睛,因此自然的相機(jī)動(dòng)線可以保證交互的自然流暢。

          Controls

          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 的 例子 供大家參考。

          相機(jī)動(dòng)線

          如果不使用 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ù)制代碼

          小結(jié)

          沒趕上 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)效方案歡迎一起探討~

          參考資料

          • Three.js文檔
          • Three.js開發(fā)指南
          • Threejs現(xiàn)學(xué)現(xiàn)賣
          • 一起炫起來 -- 3D粒子動(dòng)畫篇
          • 貝塞爾曲線算法之JS獲取點(diǎn)

          家好,很高興又見面了,我是"高級(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)入正題!

          1.HTML轉(zhuǎn)化為Word之 html-docx-js

          1.1 什么是 html-docx-js

          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)需要說明:

          • html-docx-js 適用于任何支持 Blob 的現(xiàn)代瀏覽器(無論是本機(jī)還是通過 Blob.js)。它在 Google Chrome 36、Safari 7 和 Internet Explorer 10 上進(jìn)行了測(cè)試,也適用于使用 Buffer 而不是 Blob 的 Node.js(在 v0.10.12 上測(cè)試)。
          • html-docx-js 僅支持內(nèi)聯(lián)的 base64 圖像(通過 DATA URI 獲取)。 但動(dòng)態(tài)轉(zhuǎn)換常規(guī)圖像(來自靜態(tài)文件夾)很容易,開發(fā)者可以自行完成。

          目前 Mammoth 在 Github 上通過 MIT 協(xié)議開源,有超過 1k 的 star、0.3k 的 fork、0.7k 的項(xiàng)目依賴量、NPM 周平均下載量 9k,是一個(gè)值得關(guān)注的前端開源項(xiàng)目。

          1.2 如何使用 html-docx-js

          var converted=htmlDocx.asBlob(content);
          saveAs(converted, "test.docx");

          asBlob 可以采用其他選項(xiàng)來控制文檔的頁(yè)面設(shè)置:

          • orientation:橫向或縱向(默認(rèn))
          • margins:邊距大小圖
          • top:數(shù)字(默認(rèn):1440,即 2.54 厘米)
          • right:數(shù)字(默認(rèn):1440)
          • bottom:數(shù)字(默認(rèn):1440)
          • left:數(shù)字(默認(rèn):1440)
          • header:數(shù)字(默認(rèn)值:720)
          • footer:數(shù)字(默認(rèn)值:720)
          • gutter:數(shù)字(默認(rèn)值:0)

          比如下面的例子:

          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。

          2.mammoth.js

          2.1 什么是 mammoth.js

          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 目前支持以下功能:

          • 標(biāo)題、列表、評(píng)論
          • 從自己的 docx 樣式到 HTML 的可定制映射。 例如,可以通過提供適當(dāng)?shù)臉邮接成鋵?warningHeading 轉(zhuǎn)換為 h1.warning。
          • Tables: 當(dāng)前忽略表格本身的格式(例如:邊框),但文本的格式與文檔其余部分的格式相同。
          • 腳注和尾注、圖片、粗體、斜體、下劃線、刪除線、上標(biāo)和下標(biāo)、鏈接、Line、Line breaks
          • 文本框:文本框的內(nèi)容被視為出現(xiàn)在包含文本框的段落之后的單獨(dú)段落。

          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)目。

          2.2 使用 mammoth.js

          以文檔轉(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 示例可以參考文末資料,本文不再過多展開。

          3.本文總結(jié)

          本文主要和大家介紹 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ù)雜的邏輯,輕松的幾行樣式代碼就可以搞定。

          回顧以前(js瀑布流)

          基于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代碼。

          display: flex

          關(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é)尾“了解更多”

          column-count

          關(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é)尾“了解更多”

          display: grid

          關(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é)尾“了解更多”

          總結(jié)

          通過,這3種CSS瀑布流布局,你更喜歡哪一種呢?

          個(gè)人更喜歡column-count,看起來更加清晰,容易理解,代碼量也很少。

          公告

          喜歡小編的點(diǎn)擊關(guān)注,了解更多知識(shí)!

          源碼地址請(qǐng)點(diǎn)擊下方“了解更多”


          主站蜘蛛池模板: 国精产品一区一区三区MBA下载| 精品一区二区三区3d动漫| 亚洲一区二区精品视频| 中文字幕精品一区二区三区视频| 成人国产精品一区二区网站| 中文字幕精品一区| 中文无码AV一区二区三区| 国产成人无码一区二区三区在线| 一区二区在线免费视频| 亚洲综合一区二区精品久久| 中文字幕一区二区三| 国产AⅤ精品一区二区三区久久| 国产成人精品a视频一区| 中文字幕一区在线| 鲁丝丝国产一区二区| 久久se精品一区二区| 国产熟女一区二区三区五月婷| 国产一区视频在线| 日本免费精品一区二区三区| 日韩在线一区视频| 78成人精品电影在线播放日韩精品电影一区亚洲 | 亚洲乱码一区二区三区国产精品| 欧美一区内射最近更新| 99精品一区二区三区| 久久精品人妻一区二区三区| 国产一区二区三区电影| 日韩三级一区二区三区| 中文字幕AV一区二区三区| 末成年女A∨片一区二区| 久久精品国产免费一区| 嫩B人妻精品一区二区三区| 一区二区三区免费在线观看| 精品一区二区三区四区电影| 国产精品综合一区二区| 精品国产一区在线观看| 国产成人一区二区动漫精品| 国产在线一区二区三区av| 日美欧韩一区二去三区| 精品福利一区3d动漫| 无码日韩精品一区二区人妻| 一区二区不卡视频在线观看|