亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專(zhuān)欄INFORMATION COLUMN

高仿騰訊QQ Xplan(X計(jì)劃)的H5頁(yè)面(2):動(dòng)畫(huà)控制

wyk1184 / 2895人閱讀

摘要:比如地球自轉(zhuǎn)時(shí)播放背景音樂(lè),動(dòng)畫(huà)一旦開(kāi)始則停止穿越云層后播放視頻,其他時(shí)候視頻是停止的。在上面做動(dòng)畫(huà)分析的時(shí)候,是把這個(gè)開(kāi)場(chǎng)動(dòng)畫(huà)分開(kāi)來(lái)設(shè)想的,但是上面的用上狀態(tài)機(jī)之后,意外的發(fā)現(xiàn)這個(gè)入場(chǎng)動(dòng)畫(huà)可以以另外一個(gè)放進(jìn)來(lái)。

上一篇知道如何制作threejs地球之后,就正式coding了,當(dāng)然還是使用最心愛(ài)的Vue。本篇會(huì)有一些代碼,但是都是十幾行的獨(dú)立片段,相信你不用擔(dān)心。

布局

在進(jìn)入本篇主題前,要簡(jiǎn)單看一下xplan中的自適應(yīng)解決方案,即如何在不同尺寸設(shè)備中,都保證地球最合適的大小和位置,并且與其配套的一些圖片(虛線的橢圓軌道、正中心白色的圓環(huán)等)都不會(huì)顯示的錯(cuò)位。

xplan用的方式簡(jiǎn)單直接,固定大小內(nèi)作布局,然后針對(duì)不同的設(shè)備尺寸進(jìn)行縮放。

固定畫(huà)布大?。?75 * 600),所有和地球相關(guān)的元素都可以在這個(gè)范圍內(nèi)絕對(duì)定位,之后scale一下,保證在設(shè)備實(shí)際尺寸中是被包含(contain)的。這種方式比REM等其他的自適應(yīng)方式更適合這個(gè)項(xiàng)目,畢竟threejs中不能使用REM單位。

感謝Vue,我得以將上面這個(gè)自行縮放的邏輯寫(xiě)成一個(gè)Page組件,之后再也不用操心布局問(wèn)題了。

動(dòng)畫(huà)

xplan中的動(dòng)畫(huà)是最吸引我的地方,特別是地球放大,穿越云層的那一刻,想想還有點(diǎn)小激動(dòng)。

其實(shí)之前看到過(guò)一些項(xiàng)目有做從外太空俯沖進(jìn)地球表面的動(dòng)畫(huà),但是那些基本都是純圖片制作的SpriteSheet Animation,動(dòng)畫(huà)的前進(jìn)后退控制都很容易。但xplan項(xiàng)目中則不同,動(dòng)畫(huà)過(guò)程中需要控制多個(gè)動(dòng)畫(huà)對(duì)象,還要配合其他資源(音頻和視頻)。

分析

xplan中動(dòng)畫(huà)的邏輯是,在地球自轉(zhuǎn)過(guò)程中,長(zhǎng)按按鈕,會(huì)依次發(fā)生:

地球旋轉(zhuǎn)到目的坐標(biāo)

地球放大(相機(jī)推進(jìn))到該坐標(biāo)

到足夠近的時(shí)候,播放云層穿越動(dòng)畫(huà)

云層穿越結(jié)束后,展示對(duì)應(yīng)坐標(biāo)的視頻內(nèi)容

任何時(shí)刻松開(kāi)長(zhǎng)按按鈕,動(dòng)畫(huà)都會(huì)回退到地球自轉(zhuǎn)的狀態(tài)

為了方便討論,將上面分析到的動(dòng)畫(huà)階段命名一下:

地球自轉(zhuǎn)過(guò)程:idle階段

地球轉(zhuǎn)動(dòng)到指定坐標(biāo)的過(guò)程:rotating階段

地球距離被拉近拉遠(yuǎn)的過(guò)程:zooming階段

穿越云層的過(guò)程:diving階段

云層過(guò)后的視頻展示:presenting階段

具體分析幾個(gè)過(guò)程:

在idle階段,只要touchstart,就算你只長(zhǎng)按了0.1s,那么rotating的動(dòng)畫(huà)就會(huì)完整的觸發(fā),然后狀態(tài)跳回idle(rotating沒(méi)有反向旋轉(zhuǎn))。如上示意圖。

如果長(zhǎng)按至了zooming階段,松開(kāi)手指之后,zooming動(dòng)畫(huà)會(huì)立刻反向播放,直至回到idle階段。如上示意圖。

如果zooming過(guò)程松開(kāi)手指后,但是在離開(kāi)zooming階段前再次按下去,那么zooming動(dòng)畫(huà)會(huì)再一次正向播放。如上示意圖。

diving階段貌似又回到了和rotating類(lèi)似的行為,就算中途結(jié)束,也會(huì)完成當(dāng)前階段的動(dòng)畫(huà)。但是和rotating不一樣的是,diving階段是有反向動(dòng)畫(huà)的。因此可以看到上面的示意圖。

我在考慮的過(guò)程中,陰差陽(yáng)錯(cuò)的誤以為還有一個(gè)條件:即除了rotating階段外,其他動(dòng)畫(huà)過(guò)程都可以隨時(shí)進(jìn)和退(上面的GIF就是我最終完成的動(dòng)畫(huà)控制)。這個(gè)給自己添加額外的難度,困擾了我很久。

分步實(shí)現(xiàn):地球

我創(chuàng)建了一個(gè)Earth類(lèi),負(fù)責(zé)3D地球(包括光線,光暈,地表的云,浮動(dòng)坐標(biāo)點(diǎn)等)的創(chuàng)建和渲染,同時(shí)向外提供幾個(gè)public方法:

setCameraPosition()

getCameraPosition()

startAutoRotation()

stopAutoRotation()

地球旋轉(zhuǎn)到指定坐標(biāo)點(diǎn),其實(shí)就是設(shè)置camera的position來(lái)完成了。要有流暢動(dòng)畫(huà)的感覺(jué),就使用tween去做position的更新。

new TWEEN.Tween(
  earth.getCameraPosition()
).to(
  targetCameraPosition,
  1000
).onUpdate(function () {
  earth.setCameraPosition(this.x, this.y, this.z)
})

關(guān)于tween和threejs動(dòng)畫(huà),這里有教程。

其實(shí)最開(kāi)始,這個(gè)Earth類(lèi)沒(méi)有這么純粹,我在里面加了targetLocation代表當(dāng)前要轉(zhuǎn)到的目標(biāo)地點(diǎn);還將tween的邏輯寫(xiě)在了這個(gè)類(lèi)里面,讓earth知道自己的目的地,控制自己的旋轉(zhuǎn)動(dòng)畫(huà)。但后面發(fā)現(xiàn)對(duì)于這個(gè)項(xiàng)目中動(dòng)畫(huà)可控制的靈活性,這樣封裝在內(nèi)部的動(dòng)畫(huà)邏輯,將很難寫(xiě)成清晰的代碼,讓其能和后面的云層動(dòng)畫(huà)統(tǒng)一來(lái)控制起來(lái)。

分步實(shí)現(xiàn):云層

決定使用SpriteSheet Animation類(lèi)似的方法做云層動(dòng)畫(huà)。其實(shí)有這樣的庫(kù),比如Film(這個(gè)好像也是qq下面的團(tuán)隊(duì)做的),但是我還是更想從npm中install一個(gè),由于沒(méi)有找到合適的,就索性自己寫(xiě)一個(gè)好了,于是就發(fā)布了一個(gè)小工具——image-sprite。

操作由ImageSprite類(lèi)創(chuàng)建云層對(duì)象,只用到了兩個(gè)public方法,主要控制播放前一幀和后一幀:

imageSprite.next()

imageSprite.prev()

其實(shí)應(yīng)該使用自動(dòng)播放(play)和暫停(pause)應(yīng)該也能完成,anyway

云層動(dòng)畫(huà)功能單一,想把它寫(xiě)的不純粹也難。個(gè)人覺(jué)得coding的藝術(shù)就在于如何去劃分這個(gè)純粹。

第一印象

上面兩個(gè)關(guān)鍵動(dòng)畫(huà)對(duì)象都實(shí)現(xiàn)了,用戶(hù)的行為也很簡(jiǎn)單,只有touchstart和touchend,那么用一個(gè)touchDown標(biāo)志位記錄一下就可以了。所以可以有一個(gè)中控器(controller),根據(jù)用戶(hù)產(chǎn)生的狀態(tài),來(lái)調(diào)用不同的動(dòng)畫(huà)對(duì)象播放動(dòng)畫(huà)。

最先開(kāi)始,腦子里面第一印象是下面這樣的解決方案:

function handleTouchDown () {
  touchDown = true

    if (currentState is idle) {
        playRotatingForwardAnimation(handleAnimationComplete)
    } else if (currentState is rotating) {
        playZoomingForwardAnimation(handleAnimationComplete)
    } else if (currentState is zooming) {
        playDivingForwardAnimation(handleAnimationComplete)
    } else if (currentState is diving) {
        playPresentingForwardAnimation(handleAnimationComplete)
    } else if (currentState is presenting) {
        // nothing to do
    }
}

function handleTouchEnd () {
    touchDown = false
}

function handleAnimationComplete () {
    if (touchDown) {
        // 找到下一個(gè)階段,正向播放動(dòng)畫(huà)
        findNextState()
        playForwardAnimation(handleAnimationComplete)
    } else {
        // 找到上一個(gè)階段,反向播放動(dòng)畫(huà) 
        findPrevState()
        playBackwardAnimation(handleAnimationComplete)
    }
}

這樣的方案能解決動(dòng)畫(huà)的大方向,即動(dòng)畫(huà)階段之間的前進(jìn)和后退,無(wú)法控制階段內(nèi)的每一幀的方向。而且也能看到,上面有太多的if判斷,handleTouchDown函數(shù)中的那種if情況,一定要避免,否則大項(xiàng)目中代碼很難維護(hù)。這樣的情況使用有限狀態(tài)機(jī)模式或者策略模式都是很容易解決的。

第一印象告訴我:

要使用狀態(tài)機(jī)設(shè)計(jì)模式

要從幀級(jí)別去做控制

狀態(tài)機(jī)

寫(xiě)代碼過(guò)程中肯定會(huì)遇到狀態(tài),最常見(jiàn)的狀態(tài)會(huì)被記錄成布爾值或者字符串常量,然后在做某個(gè)行為的時(shí)候?qū)顟B(tài)變量進(jìn)行if-else判斷。如果只有2個(gè)狀態(tài),還行,但是狀態(tài)如果會(huì)變多,那么這樣的代碼就很難維護(hù),將在主體中引入越來(lái)越多的if-else,越來(lái)越多的與特定狀態(tài)相關(guān)的變量和邏輯。

個(gè)人非常喜歡狀態(tài)機(jī)模式或者策略模式,它們本質(zhì)都一樣,都是使用組合代替繼承,完成統(tǒng)一接口下的行為的多樣性。最開(kāi)心的是,這個(gè)模式將混雜在主體中的狀態(tài)量和行為抽離出來(lái),多帶帶封裝,讓主體變的清清爽爽;還有,在JS中,你甚至連接口類(lèi)都不用寫(xiě)!

舉個(gè)簡(jiǎn)單的例子,上一篇中談到的ImageSprite,用來(lái)將一系列圖片進(jìn)行播放,本質(zhì)上就是繪制圖片而已。但是我這里提供兩種模式,一種繪制在canvas里,一種繪制在dom里(即image展示)。

不使用模式,可以簡(jiǎn)單的寫(xiě)成這樣:

class ImageSprite {
    constructor () {
        this.renderMode = "canvas"
        this.context = null
        this.imageElement = null
        this.images = []
    }
    drawImage () {
        if (this.renderMode === "canvas") {
            this.context.drawImage()
        } else if (this.rendererMode === "dom") {
            this.imageElement.src = "..."
        }
    }
}

使用了狀態(tài)機(jī)模式(這里的場(chǎng)景來(lái)看,叫策略模式更貼切,渲染策略不同):

class ImageSprite {
    constructor () {
        this.renderer = new CanvasRenderer(this)
        this.images = []
    }
    drawImage () {
        this.renderer.drawImage()
    }
}

class CanvasRenderer {
    constructor (imageSprite) {
        this.imageSprite = imageSprite
        this.context = null
    }
    drawImage () {
        this.context.drawImage()
    }
}

class DomRenderer {
    constructor (imageSprite) {
        this.imageSprite = imageSprite
        this.imageElement = null
    }
    drawImage () {
        this.imageElement.src = "..."
    }
}

可以看到使用了模式之后,contextimageElement這樣的和狀態(tài)相關(guān)的變量,還有繪制canvas圖片和繪制dom圖片的不同代碼,都從主體ImageSprite中抽離出去,多帶帶的封裝到了不同的狀態(tài)對(duì)象中去了。

想想一下如果有第三種渲染模式,比如渲染在webgl中去,在不使用模式的代碼中,要添加變量,要修改drawImage函數(shù);但是在使用了模式的代碼中,現(xiàn)有代碼都不用改變,只需要添加一個(gè)新類(lèi)WebglRenderer就可以了。這就是代碼的可擴(kuò)展性和可維護(hù)性的體現(xiàn)。(在Java中,還能省去代碼的重新編譯的過(guò)程)

整合

回到xplan的動(dòng)畫(huà)中去。在前面分析動(dòng)畫(huà)階段的時(shí)候,其實(shí)就得到了每個(gè)狀態(tài),這些狀態(tài)的統(tǒng)一接口就是向前幀動(dòng)畫(huà)(forward)和向后幀動(dòng)畫(huà)(backward)。

先不管每個(gè)state中邏輯該怎樣,有了約定的接口,就可以把我們的中控器(Controller)寫(xiě)個(gè)基本框架了:

class Controller {
    constructor (earth, cloud) {
        this.earth = earth
        this.cloud = cloud
        this.touchDown = false
        this.state = new IdleState(this) // 初始狀態(tài)為IdleState
        this._init()
    }
    _loop () {
        requestAnimationFrame(this._loop.bind(this))
        if (this.touchDown) { // 如果touchDown,則向前一幀
            this.state.forward()
        } else { // 否則,向后一幀
            this.state.backward()
    }
    handleTouchStart () {
        this.touchDown = true
    }
    handleTouchEnd () {
        this.touchDown = false
    }
    
    // ...
}

因?yàn)橐龅綆?jí)別的控制,因此這里用到requestAnimationFrame來(lái)制作渲染循環(huán)。代碼是不是很清晰簡(jiǎn)單!在渲染循環(huán)中,根本不在乎動(dòng)畫(huà)邏輯怎么執(zhí)行,只知道touchDown了,就做向前動(dòng)畫(huà),否則做向后動(dòng)畫(huà),其他的都在各自的狀態(tài)類(lèi)里去實(shí)現(xiàn)。

下面拿兩個(gè)狀態(tài)類(lèi)舉例,其他的請(qǐng)移步這里。

IdleState

class IdleState {
    constructor (controller) {
        this.controller = controller
    }
    forward () {
        this.controller.state = new RotatingState(this.controller)
    }
    backward () {
        // do nothing
    }
}

這里IdleState沒(méi)有向后的動(dòng)畫(huà),因此backward()里面是空的;而該狀態(tài)下的touchDown都會(huì)讓earth開(kāi)始旋轉(zhuǎn)到指定坐標(biāo),而這個(gè)過(guò)程我們知道是RotatingState該做的,所以在RotatingState的‘forward()`里會(huì)去實(shí)現(xiàn)旋轉(zhuǎn)控制。

DivingState

class DivingState {
    constructor (controller) {
        this.controller = controller
    }
    forward () {
        let cloud = this.controller.cloud
        if (cloud.currentFrame is last frame) {  // 最后一幀時(shí),進(jìn)入下一個(gè)狀態(tài)
            this.controller.state = new PresentingState(this.controller)
        } else {
            cloud.next() // 播放下一幀
        }
    }
    backward () {
        let cloud = this.controller.cloud
        if (cloud.currentFrame is first frame) {  // 回退到第一幀時(shí),進(jìn)入上一個(gè)狀態(tài)
            this.controller.state = new ZoomingState(this.controller)
        } else {
            cloud.prev() // 播放前一幀
        }
    }
}

還記得么,diving是指穿越云層的那個(gè)過(guò)程。因此它往前(forward)是presenting,往后(backward)是zooming。而什么時(shí)候切換到下一個(gè)或者前一個(gè)狀態(tài),和往前或者往后的每一幀動(dòng)畫(huà)該如何執(zhí)行,都只有這個(gè)DivingState知道,完美的邏輯封裝。

完整的動(dòng)畫(huà)邏輯里,還包含著一些音頻和視頻的控制邏輯。比如地球自轉(zhuǎn)時(shí)播放背景音樂(lè),動(dòng)畫(huà)一旦開(kāi)始則停止;穿越云層后播放視頻,其他時(shí)候視頻是停止的。這些邏輯,能夠很容易的添加到上面的狀態(tài)中去。比如在IdleState的contructor中播放音樂(lè),在RotatingState的contructor中停止播放音樂(lè);在PresentingState的constructor中播放視頻,在DivingState的contructor中停止視頻。

所以,一旦邏輯清晰了,代碼清晰了,添加功能時(shí)顯得很容易。

意外收獲

完成上面的所有動(dòng)畫(huà)狀態(tài)之后,我發(fā)現(xiàn)地球其實(shí)還有一個(gè)動(dòng)畫(huà),那就是開(kāi)場(chǎng)的逆向旋轉(zhuǎn)并放大的入場(chǎng)動(dòng)畫(huà)。在上面做動(dòng)畫(huà)分析的時(shí)候,是把這個(gè)開(kāi)場(chǎng)動(dòng)畫(huà)分開(kāi)來(lái)設(shè)想的,但是上面的controller用上狀態(tài)機(jī)之后,意外的發(fā)現(xiàn)這個(gè)入場(chǎng)動(dòng)畫(huà)可以以另外一個(gè)state放進(jìn)來(lái)。

入場(chǎng)動(dòng)畫(huà)狀態(tài)類(lèi):

class EnteringState {
  constructor (controller) {
    this.controller = controller
    this.tween = new TWEEN.Tween({
      // 起點(diǎn)位置
    }).to({
      // 終點(diǎn)位置
    }, 1600).onUpdate(function () {
      // 設(shè)置earth的縮放和旋轉(zhuǎn)
    }).onComplete(function () {
      this.controller.state = new IdleState(this.controller) // 完成后進(jìn)入IdleState
    }).easing(TWEEN.Easing.Cubic.Out).start()
  }
  forward () {
    TWEEN.update()
  }
    backward () {
        // do nothing
    }
}

最后將Controller初始化時(shí)的第一個(gè)state賦值改為EnteringState即可。這真算是一個(gè)意外的收獲,本來(lái)是打算多帶帶(在controller之外)去實(shí)現(xiàn)的。

小結(jié)

到這里就差不多了,xplan主要的東西都講到了,高(shan)仿(zhai)的過(guò)程還不錯(cuò),了解了three,順便還publish了幾個(gè)小的工具庫(kù);有不足、也有超越。這個(gè)h5看似復(fù)雜,但是技術(shù)也沒(méi)有多高深,主要還是創(chuàng)意,還是要給xplan點(diǎn)個(gè)贊!

最后,個(gè)人接h5,有沒(méi)有個(gè)人或者公司啊,不要不好意思聯(lián)系我~

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/83376.html

相關(guān)文章

  • 高仿騰訊QQ XplanX計(jì)劃H5頁(yè)面(1):threejs創(chuàng)建地球

    摘要:首先是這個(gè)地球,得看看它是真還是假因?yàn)楹芏嘈Ч悄醚┍虉D做的,比如這里的旋轉(zhuǎn)的飛機(jī),結(jié)果找到了并且在網(wǎng)站文件中搜到了,那就是沒(méi)跑了。 上個(gè)月底,在朋友圈看到一個(gè)號(hào)稱(chēng)這可能是地球上最美的h5的分享,點(diǎn)進(jìn)入后發(fā)現(xiàn)這個(gè)h5還很別致,思考了一會(huì),決定要不高仿一個(gè)? 到今天為止,高仿基本完成, 線上地址 github地址 除了手機(jī)端的media控制沒(méi)有去兼容,其他的基本都給仿了。 那為了讓你...

    MartinHan 評(píng)論0 收藏0
  • SegmentFault 技術(shù)周刊 Vol.35 - WebGL:打開(kāi)網(wǎng)頁(yè)看大片

    摘要:在文末,我會(huì)附上一個(gè)可加載的模型方便學(xué)習(xí)中文藝術(shù)字渲染用原生可以很容易地繪制文字,但是原生提供的文字效果美化功能十分有限。 showImg(https://segmentfault.com/img/bVWYnb?w=900&h=385); WebGL 可以說(shuō)是 HTML5 技術(shù)生態(tài)鏈中最為令人振奮的標(biāo)準(zhǔn)之一,它把 Web 帶入了 3D 的時(shí)代。 初識(shí) WebGL 先通過(guò)幾個(gè)使用 Web...

    objc94 評(píng)論0 收藏0
  • 新穎交互形式H5案例淺析(技術(shù)分析)

    摘要:那我這邊呢,根據(jù)技術(shù)的分類(lèi),找出其中十個(gè)有代表性的案例,給大家解析一下他們技術(shù)的實(shí)現(xiàn)方案。經(jīng)過(guò)我對(duì)線上的代碼進(jìn)行修改,使這個(gè)頁(yè)面在安卓端強(qiáng)制使用來(lái)進(jìn)行展示后發(fā)現(xiàn),在播放了一會(huì)后微信瀏覽器直接崩潰。那么這十個(gè)案例的淺析就完了,謝謝。 最近我們前端這邊搜集了50個(gè)比較優(yōu)秀的H5。 那我這邊呢,根據(jù)技術(shù)的分類(lèi),找出其中十個(gè)有代表性的案例,給大家解析一下他們技術(shù)的實(shí)現(xiàn)方案。 設(shè)計(jì)師也可以根據(jù)技...

    hikui 評(píng)論0 收藏0
  • 新穎交互形式H5案例淺析(技術(shù)分析)

    摘要:那我這邊呢,根據(jù)技術(shù)的分類(lèi),找出其中十個(gè)有代表性的案例,給大家解析一下他們技術(shù)的實(shí)現(xiàn)方案。經(jīng)過(guò)我對(duì)線上的代碼進(jìn)行修改,使這個(gè)頁(yè)面在安卓端強(qiáng)制使用來(lái)進(jìn)行展示后發(fā)現(xiàn),在播放了一會(huì)后微信瀏覽器直接崩潰。那么這十個(gè)案例的淺析就完了,謝謝。 最近我們前端這邊搜集了50個(gè)比較優(yōu)秀的H5。 那我這邊呢,根據(jù)技術(shù)的分類(lèi),找出其中十個(gè)有代表性的案例,給大家解析一下他們技術(shù)的實(shí)現(xiàn)方案。 設(shè)計(jì)師也可以根據(jù)技...

    lpjustdoit 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<