cocos2dx骨骼動(dòng)畫Armature源碼剖析(三)
cocos2dx里骨骼動(dòng)畫代碼在cocos -> editor-support -> cocostudio文件夾中,win下通過篩選器,文件結(jié)構(gòu)如下。(mac下沒有分,是整個(gè)一坨)
armature(目錄): animation(目錄):動(dòng)畫控制相關(guān)。 CCProcessBase(文件): ProcessBase(類):CCTween和ArmatureAnimation的基類。 CCTWeen(文件): Tween(類):控制flash里一個(gè)layer的動(dòng)畫。 CCArmatureAnimation(文件): ArmatureAnimation(類):控制整個(gè)動(dòng)畫,內(nèi)有多個(gè)Tween。 datas(目錄):xml或json轉(zhuǎn)成c++中直接用的數(shù)據(jù)結(jié)構(gòu)。 CCDatas(文件): BaseData(類):BoneData、FrameData的基類,包含大小位置顏色等信息。 DisplayData(類): SpriteDisplayData、ArmatureDisplayData、ParticleDisplayData的基類。 SpriteDisplayData(類):骨骼中的顯示數(shù)據(jù)。 ArmatureDisplayData(類): ParticleDisplayData(類): BoneData(類):單個(gè)骨骼數(shù)據(jù),flash中一個(gè)layer是一個(gè)骨骼。 ArmatureData(類):骨骼數(shù)據(jù),整個(gè)骨骼結(jié)構(gòu)數(shù)據(jù)。 FrameData(類):關(guān)鍵幀數(shù)據(jù)。 MovementBoneData(類):帶有關(guān)鍵幀的骨骼數(shù)據(jù)。 MovementData(類):一個(gè)完整動(dòng)畫數(shù)據(jù)。 AnimationData(類):組動(dòng)畫數(shù)據(jù),包含多個(gè)MovementData。 ContourData(類): TextureData(類):顯示圖片數(shù)據(jù)。 utils(目錄): CCArmatureDataManager(文件): RelativeData(類): ArmatureDataManager(類):管理ArmatureData、AnimationData、TextureData。 CCArmatureDefine(文件): CCDataReaderHelper(文件): _AsyncStruct(類): _DataInfo(類): DataReaderHelper(類):這正解析xml或json的類。 CCSpriteFrameCacheHelper(文件): SpriteFrameCacheHelper(類): CCTransformHelp(文件): TransformHelp(類):矩陣運(yùn)算。 CCUtilMath(文件): CCArmature(文件): Armature(類):控制整個(gè)骨骼動(dòng)畫,內(nèi)有ArmatureAnimation和ArmatureData。 CCBone(文件): Bone(類):骨骼控制類 display(目錄):顯示的圖片管理。 CCBatchNode(文件): BatchNode(類): CCDecorativeDisplay(文件): DecorativeDisplay(類): CCDisplayFactory(文件): DisplayFactory(類): CCDisplayManager(文件): DisplayManager(類): CCSkin(文件): Skin(類): physics(目錄):物理引擎相關(guān),不分析。 ColliderFilter(文件): ColliderFilter(類): ColliderBody(類): ColliderDetecotor(類)
數(shù)據(jù)相關(guān)源碼
從底層到高層分析一個(gè)類一個(gè)類分析
再來看下數(shù)據(jù)相關(guān)的UML,總體來說,就是ArmatureDataManager依賴DataReaderHelper把flash導(dǎo)出的xml文件解析成程序直接用的XXData,XXData對(duì)應(yīng)于xml的某個(gè)節(jié)點(diǎn),比如FrameData就對(duì)應(yīng)于<f>節(jié)點(diǎn)(<animaton><mov><b><f>)。
BaseData
BaseData:用來表示骨骼或幀的位置、旋轉(zhuǎn)、顏色、縮放。
BaseData.h class BaseData : public cocosd::Ref { public: //Calculate two BaseData's between value(to - from) and set to self virtual void subtract(BaseData *from, BaseData *to, bool limit); public: //位置,xml的x,y float x; float y; //xml中z int zOrder; //旋轉(zhuǎn),xml的kX,kY float skewX; float skewY; //縮放,xml的cX,cY float scaleX; float scaleY; //啥?? float tweenRotate; //顏色的變化屬性 bool isUseColorInfo; int a, r, g, b; };
作為FrameData和BoneData的基類,提供骨骼的狀態(tài)信息。從下文可知BoneData對(duì)應(yīng)xml中的<armature<b>>中的b節(jié)點(diǎn),F(xiàn)rameData對(duì)應(yīng)xml中的<f>節(jié)點(diǎn),BoneData和FrameData都有
<x,y,kX,kY,cX,cY,pX,pY,z>等屬性,BaseDa代表了這些屬性。
BoneData
BoneData對(duì)應(yīng)xml中的<armature<b>>中的b節(jié)點(diǎn)
class BoneData : public BaseData { public: void addDisplayData(DisplayData *displayData); DisplayData *getDisplayData(int index); public: std::string name; //! the bone's name std::string parentName; //! the bone parent's name //! save DisplayData informations for the Bone cocosd::Vector<DisplayData*> displayDataList; //仿射變換,程序里好像沒用這個(gè)屬性 cocosd::AffineTransform boneDataTransform; };
BoneData里有displayDataList,用來放這個(gè)骨頭上的皮膚(就是DisplayData), DisplayData對(duì)應(yīng)xml節(jié)點(diǎn)中的<b<d>>節(jié)點(diǎn),一個(gè)BoneData里可以有多個(gè)皮膚,換裝等功能需要多個(gè)皮膚。
FrameData
FrameData對(duì)應(yīng)xml中的<f>節(jié)點(diǎn),就是flash里的關(guān)鍵幀信息。
class FrameData : public BaseData { public: int frameID; //xml中dr,這一幀長度 int duration; //不知要他干啥 bool isTween; //xml中dI,顯示哪個(gè)圖 int displayIndex; };
DisplayData
DisplayData是SpriteDisplayData、ArmatureDisplayData、ParticleDisplayData的父類,用來表示展示節(jié)點(diǎn)信息。
ArmatureData
ArmatureData是對(duì)應(yīng)<armature>節(jié)點(diǎn),里面有這個(gè)骨骼的所有骨頭,可以看成骨骼動(dòng)畫的骨骼。
class ArmatureData : public cocosd::Ref { public: //添加骨骼信息 void addBoneData(BoneData *boneData); BoneData *getBoneData(const std::string& boneName); public: std::string name; //多個(gè)骨頭信息 cocosd::Map<std::string, BoneData*> boneDataDic; float dataVersion; };
AnimationData
AnimationData對(duì)應(yīng)<animation>節(jié)點(diǎn),里面有多個(gè)MovementData,MovementData(下面介紹)對(duì)應(yīng)xml中的mov,為flash中的一個(gè)帶幀標(biāo)簽的動(dòng)畫。
class AnimationData : public cocosd::Ref { public: void addMovement(MovementData *movData); MovementData *getMovement(const std::string& movementName); ssize_t getMovementCount(); public: //<animation name="Dragon">中的name std::string name; //所有帶幀標(biāo)簽的動(dòng)畫map cocosd::Map<std::string, MovementData*> movementDataDic; //所有帶幀標(biāo)簽的動(dòng)畫名 std::vector<std::string> movementNames; };
MovementData
MovementData對(duì)應(yīng)xml中<animation<mov>>, 其中有所有的帶幀信息的骨骼MovementBoneData(mov中的b)。
class MovementData : public cocosd::Ref { public: void addMovementBoneData(MovementBoneData *movBoneData); MovementBoneData *getMovementBoneData(const std::string& boneName); public: std::string name; //xml 中 dr int duration; //這怎么有個(gè)scale?? float scale; //xml中to int durationTo; //xml中drTW int durationTween; //xml中l(wèi)p bool loop; //帶幀信息的骨骼 cocosd::Map<std::string, MovementBoneData*> movBoneDataDic; };
MovementBoneData
MovementBoneData對(duì)應(yīng)xml中<mov<b>>的b,里面有frameList,即為關(guān)鍵幀信息。
class MovementBoneData : public cocosd::Ref { void addFrameData(FrameData *frameData); FrameData *getFrameData(int index); public: //xml中的dl float delay; //xml中的sc float scale; //這個(gè)和MovementData中的duration是不是一個(gè)?? float duration; std::string name; //關(guān)鍵幀信息 cocosd::Vector<FrameData*> frameList; };
小總結(jié)
xml中的各個(gè)節(jié)點(diǎn)和XXData的對(duì)應(yīng)關(guān)系如下表,xml各個(gè)字段的意義可以參考上篇文章
再來看產(chǎn)生動(dòng)畫相關(guān)的代碼
ArmatureDataManager
ArmatureDataManager利用DataReaderHelper解析出armarureDatas、animationDatas和_textureDatas。
ArmatureDataManager是個(gè)單例,用到動(dòng)畫時(shí)會(huì)到ArmatureDataManager取得要生成動(dòng)畫的數(shù)據(jù)。
class ArmatureDataManager : public cocosd::Ref { public: //單例 static ArmatureDataManager *getInstance(); static void destroyInstance(); public: void addArmatureData(const std::string& id, ArmatureData *armatureData, const std::string& configFilePath = ""); ArmatureData *getArmatureData(const std::string& id); void removeArmatureData(const std::string& id); void addAnimationData(const std::string& id, AnimationData *animationData, const std::string& configFilePath = ""); AnimationData *getAnimationData(const std::string& id); void removeAnimationData(const std::string& id); void addTextureData(const std::string& id, TextureData *textureData, const std::string& configFilePath = ""); TextureData *getTextureData(const std::string& id); void removeTextureData(const std::string& id); void addArmatureFileInfo(const std::string& configFilePath); const cocosd::Map<std::string, ArmatureData*>& getArmatureDatas() const; const cocosd::Map<std::string, AnimationData*>& getAnimationDatas() const; const cocosd::Map<std::string, TextureData*>& getTextureDatas() const; protected: void addRelativeData(const std::string& configFilePath); RelativeData *getRelativeData(const std::string& configFilePath); private: cocosd::Map<std::string, ArmatureData*> _armarureDatas; cocosd::Map<std::string, AnimationData*> _animationDatas; cocosd::Map<std::string, TextureData*> _textureDatas; std::unordered_map<std::string, RelativeData> _relativeDatas; };
主要就是armarureDatas、animationDatas、_textureDatas三個(gè)map,那這三個(gè)map是怎么產(chǎn)生的呢?當(dāng)執(zhí)行
ArmatureDataManager::getInstance()->addArmatureFileInfo(“dragon.xml”);
后,那三個(gè)map變生成了。addArmatureFileInfo代碼如下
void ArmatureDataManager::addArmatureFileInfo(const std::string& configFilePath) { addRelativeData(configFilePath); _autoLoadSpriteFile = true; DataReaderHelper::getInstance()->addDataFromFile(configFilePath); }
又調(diào)用了DataReaderHelper::getInstance()->addDataFromFile(),可知是DataReaderHelper真正完成了數(shù)據(jù)的解析。
DataReaderHelper類里有一堆decodeXXX()(比如decodeArmature、decodeBone)解析xml的某個(gè)節(jié)點(diǎn)。看下
addDataFromFile這個(gè)代碼:
void DataReaderHelper::addDataFromFile(const std::string& filePath) { //省略一些代碼 DataInfo dataInfo; dataInfo.filename = filePathStr; dataInfo.asyncStruct = nullptr; dataInfo.baseFilePath = basefilePath; if (str == ".xml") { DataReaderHelper::addDataFromCache(contentStr, &dataInfo); } else if(str == ".json" || str == ".ExportJson") { DataReaderHelper::addDataFromJsonCache(contentStr, &dataInfo); } else if(isbinaryfilesrc) { DataReaderHelper::addDataFromBinaryCache(contentStr.c_str(),&dataInfo); } CC_SAFE_DELETE_ARRAY(pBytes); }
對(duì)應(yīng)不同的文件(xml、json、二進(jìn)制)解析方式,xml用到是addDataFromCache
void DataReaderHelper::addDataFromCache(const std::string& pFileContent, DataInfo *dataInfo) { tinyxml::XMLDocument document; document.Parse(pFileContent.c_str()); tinyxml::XMLElement *root = document.RootElement(); CCASSERT(root, "XML error or XML is empty."); root->QueryFloatAttribute(VERSION, &dataInfo->flashToolVersion); /* * Begin decode armature data from xml */ tinyxml::XMLElement *armaturesXML = root->FirstChildElement(ARMATURES); tinyxml::XMLElement *armatureXML = armaturesXML->FirstChildElement(ARMATURE); while(armatureXML) { ArmatureData *armatureData = DataReaderHelper::decodeArmature(armatureXML, dataInfo); if (dataInfo->asyncStruct) { _dataReaderHelper->_addDataMutex.lock(); } ArmatureDataManager::getInstance()->addArmatureData(armatureData->name.c_str(), armatureData, dataInfo->filename.c_str()); armatureData->release(); if (dataInfo->asyncStruct) { _dataReaderHelper->_addDataMutex.unlock(); } armatureXML = armatureXML->NextSiblingElement(ARMATURE); } /* * Begin decode animation data from xml */ tinyxml::XMLElement *animationsXML = root->FirstChildElement(ANIMATIONS); tinyxml::XMLElement *animationXML = animationsXML->FirstChildElement(ANIMATION); while(animationXML) { AnimationData *animationData = DataReaderHelper::decodeAnimation(animationXML, dataInfo); if (dataInfo->asyncStruct) { _dataReaderHelper->_addDataMutex.lock(); } ArmatureDataManager::getInstance()->addAnimationData(animationData->name.c_str(), animationData, dataInfo->filename.c_str()); animationData->release(); if (dataInfo->asyncStruct) { _dataReaderHelper->_addDataMutex.unlock(); } animationXML = animationXML->NextSiblingElement(ANIMATION); } /* * Begin decode texture data from xml */ tinyxml::XMLElement *texturesXML = root->FirstChildElement(TEXTURE_ATLAS); tinyxml::XMLElement *textureXML = texturesXML->FirstChildElement(SUB_TEXTURE); while(textureXML) { TextureData *textureData = DataReaderHelper::decodeTexture(textureXML, dataInfo); if (dataInfo->asyncStruct) { _dataReaderHelper->_addDataMutex.lock(); } ArmatureDataManager::getInstance()->addTextureData(textureData->name.c_str(), textureData, dataInfo->filename.c_str()); textureData->release(); if (dataInfo->asyncStruct) { _dataReaderHelper->_addDataMutex.unlock(); } textureXML = textureXML->NextSiblingElement(SUB_TEXTURE); } }
里面有三個(gè)while,分別decodeArmature、decodeAnimation、decodeTexture,生成ArmatureData、AnimationData、TextureData之后又ArmatureDataManager::getInstance()->addArmatureData、addAnimationData、addTextureData,加到ArmatureDataManager對(duì)應(yīng)map里。decodeXXX里又會(huì)調(diào)用各種decodeXX來生成相應(yīng)的XXXData。
Armature
在載入了xml數(shù)據(jù)后,調(diào)用
armature = Armature::create("Dragon"); armature->getAnimation()->play("walk"); armature->getAnimation()->setSpeedScale(); armature->setPosition(VisibleRect::center().x, VisibleRect::center().y * .f); armature->setScale(.f); addChild(armature);
便展示了動(dòng)畫,那么這是如何做到的呢?
Armature部分代碼如下,ArmatureAnimation控制xml的mov節(jié)點(diǎn),Bone中有Tween,這個(gè)Tween對(duì)應(yīng)xml中b(MovementBoneData)
class Armature: public cocosd::Node, public cocosd::BlendProtocol { protected: //要展示動(dòng)畫的ArmatureData ArmatureData *_armatureData; BatchNode *_batchNode; Bone *_parentBone; float _version; mutable bool _armatureTransformDirty; //所有Bone cocosd::Map<std::string, Bone*> _boneDic; cocosd::Vector<Bone*> _topBoneList; cocosd::BlendFunc _blendFunc; cocosd::Vec _offsetPoint; cocosd::Vec _realAnchorPointInPoints; //動(dòng)畫控制器 ArmatureAnimation *_animation; };
Bone
部分代碼如下,tweenData為當(dāng)前Bone的狀態(tài),每幀都會(huì)更新這個(gè)值,并用tweenData確定worldInfo,提供Skin顯示信息。tween為骨頭的整個(gè)動(dòng)畫過程。
class Bone: public cocosd::Node { protected: BoneData *_boneData; //! A weak reference to the Armature Armature *_armature; //! A weak reference to the child Armature Armature *_childArmature; DisplayManager *_displayManager; /* * When Armature play an animation, if there is not a MovementBoneData of this bone in this MovementData, this bone will be hidden. * Set IgnoreMovementBoneData to true, then this bone will also be shown. */ bool _ignoreMovementBoneData; cocosd::BlendFunc _blendFunc; bool _blendDirty; Tween *_tween; //! Calculate tween effect //! Used for making tween effect in every frame FrameData *_tweenData; Bone *_parentBone; //! A weak reference to its parent bool _boneTransformDirty; //! Whether or not transform dirty //! self Transform, use this to change display's state cocosd::Mat _worldTransform; BaseData *_worldInfo; //! Armature's parent bone Bone *_armatureParentBone; };
Tween
這個(gè)是每個(gè)骨頭的動(dòng)畫過程,見下面的movementBoneData。tweenData是Bone中tweenData的引用,在這每幀會(huì)計(jì)算這個(gè)tweenData值。
class Tween : public ProcessBase{ protected: //! A weak reference to the current MovementBoneData. The data is in the data pool MovementBoneData *_movementBoneData; FrameData *_tweenData; //! The computational tween frame data, //! A weak reference to the Bone's tweenData FrameData *_from; //! From frame data, used for calculate between value FrameData *_to; //! To frame data, used for calculate between value // total diff guan FrameData *_between; //! Between frame data, used for calculate current FrameData(m_pNode) value Bone *_bone; //! A weak reference to the Bone TweenType _frameTweenEasing; //! Dedermine which tween effect current frame use int _betweenDuration; //! Current key frame will last _betweenDuration frames // 總共運(yùn)行了多少幀 guan int _totalDuration; int _fromIndex; //! The current frame index in FrameList of MovementBoneData, it's different from m_iFrameIndex int _toIndex; //! The next frame index in FrameList of MovementBoneData, it's different from m_iFrameIndex ArmatureAnimation *_animation; bool _passLastFrame; //! If current frame index is more than the last frame's index };
ArmatureAnimation
控制動(dòng)畫的播放,看到_tweenList,所有骨頭的集合就是動(dòng)畫了。
class ArmatureAnimation : public ProcessBase { protected: //! AnimationData save all MovementDatas this animation used. AnimationData *_animationData; MovementData *_movementData; //! MovementData save all MovementFrameDatas this animation used. Armature *_armature; //! A weak reference of armature std::string _movementID; //! Current movment's name int _toIndex; //! The frame index in MovementData->m_pMovFrameDataArr, it's different from m_iFrameIndex. cocos2d::Vector<Tween*> _tweenList; }
如何做到每幀更新骨頭的信息?
addChild(armature)后,Armaure中的onEnter(node進(jìn)入舞臺(tái)就會(huì)調(diào)用,比如addchild),onEnter調(diào)scheduleUpdate調(diào)scheduleUpdateWithPriority調(diào)_scheduler->scheduleUpdate。這樣就每幀調(diào)用armature的update。
void Armature::update(float dt) { _animation->update(dt); for(const auto &bone : _topBoneList) { bone->update(dt); } _armatureTransformDirty = false; }
又調(diào)用了animation->update(dt);及遍歷調(diào)用bone->update(dt);animation->update(dt)如下:
void ArmatureAnimation::update(float dt) { ProcessBase::update(dt); for (const auto &tween : _tweenList) { tween->update(dt); } //省略一堆代碼 }
又調(diào)用了tween->update(dt); 每一個(gè)update都會(huì)調(diào)用updateHandler(ProcessBase中update調(diào)用了update里調(diào)用updateHandler)
void Tween::updateHandler() { //省略一堆代碼 if (_loopType > ANIMATION_TO_LOOP_BACK) { percent = updateFrameData(percent); } if(_frameTweenEasing != ::cocosd::tweenfunc::TWEEN_EASING_MAX) { tweenNodeTo(percent); } }
tweenNodeTo調(diào)用了tweenNodeTo,其中的tweenData其實(shí)就是Bone的tweenData。根據(jù)percent計(jì)算了_tweenData的變化量。
FrameData *Tween::tweenNodeTo(float percent, FrameData *node) { node = node == nullptr ? _tweenData : node; if (!_from->isTween) { percent = ; } node->x = _from->x + percent * _between->x; node->y = _from->y + percent * _between->y; node->scaleX = _from->scaleX + percent * _between->scaleX; node->scaleY = _from->scaleY + percent * _between->scaleY; node->skewX = _from->skewX + percent * _between->skewX; node->skewY = _from->skewY + percent * _between->skewY; _bone->setTransformDirty(true); if (node && _between->isUseColorInfo) { tweenColorTo(percent, node); } return node; }
轉(zhuǎn)了一大圈終于在每幀更新了Bone中的tweenData,最后看Bone的update,其根據(jù)tweenData計(jì)算了worldInfo、worldTransform。而且updateDisplay更新skin的信息,staticcast<skin*>(display)->updateArmatureTransform();再transform = TransformConcat(_bone->getNodeToArmatureTransform(), _skinTransform);
void Bone::update(float delta) { if (_parentBone) _boneTransformDirty = _boneTransformDirty || _parentBone->isTransformDirty(); if (_armatureParentBone && !_boneTransformDirty) { _boneTransformDirty = _armatureParentBone->isTransformDirty(); } if (_boneTransformDirty) { if (_dataVersion >= VERSION_COMBINED) { TransformHelp::nodeConcat(*_tweenData, *_boneData); _tweenData->scaleX -= ; _tweenData->scaleY -= ; } _worldInfo->copy(_tweenData); _worldInfo->x = _tweenData->x + _position.x; _worldInfo->y = _tweenData->y + _position.y; _worldInfo->scaleX = _tweenData->scaleX * _scaleX; _worldInfo->scaleY = _tweenData->scaleY * _scaleY; _worldInfo->skewX = _tweenData->skewX + _skewX + _rotationZ_X; _worldInfo->skewY = _tweenData->skewY + _skewY - _rotationZ_Y; if(_parentBone) { applyParentTransform(_parentBone); } else { if (_armatureParentBone) { applyParentTransform(_armatureParentBone); } } TransformHelp::nodeToMatrix(*_worldInfo, _worldTransform); if (_armatureParentBone) { _worldTransform = TransformConcat(_worldTransform, _armature->getNodeToParentTransform()); } } DisplayFactory::updateDisplay(this, delta, _boneTransformDirty || _armature->getArmatureTransformDirty()); for(const auto &obj: _children) { Bone *childBone = static_cast<Bone*>(obj); childBone->update(delta); } _boneTransformDirty = false;
如何展示(draw)出圖片(skin)
Armature詩歌node,加入父節(jié)點(diǎn)后會(huì)調(diào)用其draw函數(shù),遍歷draw了bone的顯示元素。
void Armature::draw(cocosd::Renderer *renderer, const Mat &transform, uint_t flags) { if (_parentBone == nullptr && _batchNode == nullptr) { // CC_NODE_DRAW_SETUP(); } for (auto& object : _children) { if (Bone *bone = dynamic_cast<Bone *>(object)) { Node *node = bone->getDisplayRenderNode(); if (nullptr == node) continue; switch (bone->getDisplayRenderNodeType()) { case CS_DISPLAY_SPRITE: { Skin *skin = static_cast<Skin *>(node); skin->updateTransform(); BlendFunc func = bone->getBlendFunc(); if (func.src != _blendFunc.src || func.dst != _blendFunc.dst) { skin->setBlendFunc(bone->getBlendFunc()); } else { skin->setBlendFunc(_blendFunc); } skin->draw(renderer, transform, flags); } break; case CS_DISPLAY_ARMATURE: { node->draw(renderer, transform, flags); } break; default: { node->visit(renderer, transform, flags); // CC_NODE_DRAW_SETUP(); } break; } } else if(Node *node = dynamic_cast<Node *>(object)) { node->visit(renderer, transform, flags); // CC_NODE_DRAW_SETUP(); } } }
再skin->draw(renderer, transform, flags);會(huì)用到剛剛更新的_quad,顯示出最新的圖片信息。
{ Mat mv = Director::getInstance()->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); //TODO implement z order _quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, , mv); renderer->addCommand(&_quadCommand); }
至此,大家對(duì)cocos2dx里的骨骼動(dòng)畫應(yīng)該有了全面的認(rèn)識(shí),三篇文章介紹的比較粗糙,其實(shí)有些細(xì)節(jié)內(nèi)容我也沒看懂,不過不要在意這些細(xì)節(jié),沒有實(shí)際的改動(dòng)需求的話,懂80%就可以了,細(xì)節(jié)可以需要的時(shí)候在仔細(xì)理解。
相關(guān)文章
echarts報(bào)錯(cuò)Cannot?read?properties?of?null?(reading?‘getA
最近在開發(fā)Echarts忽然遇到了個(gè)問題,這篇文章主要給大家介紹了關(guān)于echarts報(bào)錯(cuò)Cannot?read?properties?of?null?(reading?‘getAttribute‘)的解決方法,需要的朋友可以參考下2023-01-01如何在JavaScript中實(shí)現(xiàn)私有屬性的寫類方式(二)
這篇文章主要介紹了如何在JavaScript中實(shí)現(xiàn)私有屬性的寫類方式。需要的朋友可以過來參考下,希望對(duì)大家有所幫助2013-12-12javascript獲取鼠標(biāo)位置部分的實(shí)例代碼(兼容IE,FF)
這篇文章介紹了javascript獲取鼠標(biāo)位置部分的實(shí)例代碼,有需要的朋友可以參考一下2013-08-08JS使用遮罩實(shí)現(xiàn)點(diǎn)擊某區(qū)域以外時(shí)彈窗的彈出與關(guān)閉功能示例
這篇文章主要介紹了JS使用遮罩實(shí)現(xiàn)點(diǎn)擊某區(qū)域以外時(shí)彈窗的彈出與關(guān)閉功能,結(jié)合實(shí)例形式分析了javascript事件響應(yīng)及頁面元素屬性動(dòng)態(tài)操作彈出與關(guān)閉遮罩層相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2018-07-07簡單了解JavaScript彈窗實(shí)現(xiàn)代碼
這篇文章主要介紹了簡單了解JavaScript彈窗實(shí)現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05