Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/437.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 为什么自定义元素API会创建重复的元素?_Javascript_Polymer_Web Component_Custom Element - Fatal编程技术网

Javascript 为什么自定义元素API会创建重复的元素?

Javascript 为什么自定义元素API会创建重复的元素?,javascript,polymer,web-component,custom-element,Javascript,Polymer,Web Component,Custom Element,我使用的是文档.registereElementAPI(Chrome自带),带有一些标记,如下面使用运动场景和运动节点自定义元素的标记 <body> <div id="one" style="width: 500px; height: 500px; outline: 1px solid green; float: left;"> <motor-scene id="foo"> <motor-node

我使用的是
文档.registereElement
API(Chrome自带),带有一些标记,如下面使用
运动场景
运动节点
自定义元素的标记

<body>
    <div id="one" style="width: 500px; height: 500px; outline: 1px solid green; float: left;">
        <motor-scene id="foo">
            <motor-node
                rotation="[0,30,0]"
                absoluteSize="[100, 100, 0]"
                position="[300, 100, 20]"
                opacity="0.5"
                sizeMode="[absolute, absolute, absolute]">

                <div style="background: pink; width: 100%; height: 100%;">
                    <h2>Hello HTML!</h2>
                </div>

                <motor-node
                    rotation="[30,0,0]"
                    absoluteSize="[50, 50, 0]"
                    position="[50, -70, -200]"
                    opacity="0.5"
                    sizeMode="[absolute, absolute, absolute]">

                    <div style="background: teal; width: 100%; height: 100%;">
                        Hello HTML!
                    </div>
                </motor-node>

            </motor-node>
        </motor-scene>
    </div>
    <div id="two" style="width: 500px; height: 500px; outline: 1px solid green; float: left;">
    </div>
</body>

以下是两个自定义元素(也包括)的定义,以供参考:

运动节点:
从“../motor/Scene”导入场景
从“../motor/Node”导入节点
从“../jss”导入jss
/**
*@class-MotorHTMLNode
*/
const MotorHTMLNode=document.registerement('motor-node'{
原型:Object.assign(Object.create(HTMLElement.prototype)){
createdCallback(){
console.log('createdCallback()')
此项。_attached=false
this.attachPromise=null
此值为.\u cleanedUp=true
this.node=this.makeNode()
this.createChildObserver()
this.childObserver.observe(this,{childList:true})
//TODO:mountPromise适用于节点,而不仅仅是场景。
如果(this.nodeName=='MOTOR-SCENE'){
//XXX:“承诺”与“准备就绪”:
//
//“ready”在HTML方面似乎更直观,因为
//如果用户有对电机节点或电机场景的引用
//并且它存在于DOM中,那么它已经从
//HTML API透视图。也许我们可以使用“mountPromise”来
//命令式API和HTML API的“就绪”。例如:
//
//等待$('motor-scene')[0]。准备好//使用HTML API时
//使用命令式API时等待node.mountPromise//
//
//或者,我们可以在这两种情况下都使用“ready”?。。。
this.mountPromise=this.node.mountPromise
这个
}
},
makeNode(){
返回新节点
},
createChildObserver(){
this.childObserver=新突变观察者(突变=>{
突变。forEach(突变=>{
让节点=Array.from(mutation.addedNodes)
节点=节点。过滤器(节点=>{
让我们保持真实
如果(
node.nodeName.match(/^MOTOR-/)
|| (
//忽略MotorDomainSceneContainer,因为我们
//不会移动它(应该留在里面的地方)
//元素的名称)。其他元素
//移动到场景图中(例如,如果
//你把a放在a里面,然后
//它被移植到马达中
//场景图DOM树,它植根于
//.你会明白这意味着什么
//现在,如果您查看元素检查器。
node.className//某些节点没有类名(#text、#comment、#document)。
&&node.className.match(/^MotorDomainSceneContainer/)
)
) {
保留=错误
}
回程保留
})
nodes.forEach(node=>{
//这是一种黑客行为:我们删除内容
//从实际DOM中的motor节点
//它位于节点控制元素中,该元素可能
//使调试变得更加困难,但至少
//就目前而言,它是有效的。
this.node.element.element.appendChild(节点)
})
})
})
},
异步attachedCallback(){
log('attachedCallback()')
这是真的
//如果当前正在连接节点,请等待连接完成
//在再次连接之前,为避免竞争条件。这将
//几乎从来没有发生过,但以防万一,它可以防止
//最终用户端的幼稚编程(例如,如果附加
//将motor节点元素移动到DOM,然后将其移动到新元素
//在同一时间内。
等着吧,我答应你
this.attachPromise=新承诺(异步(解析)=>{
如果(此项已清除){
此参数为.\u cleanedUp=false
this.childObserver.observe(this,{childList:true})
}
//场景没有要附加到的父对象。
if(this.nodeName.toLowerCase()!=‘马达场景’)
this.parentNode.node.addChild(this.node)
解决()
})
},
异步detachedCallback(){
log('detachedCallback()')
此项。_attached=false
//如果当前正在连接节点,请等待连接完成
//在开始分离过程之前(以避免争用情况)。
//如果this.attachPromise为空,则执行将继续,而不进行任何更改
//(托多:这是我们可以信赖的吗
//在语言规范中?)。
如果(这个附件承诺)等待这个附件承诺
this.attachPromise=null
//XXX为性能,请推迟到清理前的下一个滴答声
//如果该元素实际被重新连接到其他位置
//在同一刻度内(分离和连接是同步的,
//因此,通过推迟到下一个滴答声,我们将能够知道
//元件是否重新连接,以便清理或重新连接
console.log('<motor-scene> element registered?', document.createElement('motor-scene').constructor.name == 'motor-scene')
console.log('<motor-node> element registered?', document.createElement('motor-node').constructor.name == 'motor-node')
import Scene from '../motor/Scene'
import Node from '../motor/Node'

import jss from '../jss'

/**
 * @class MotorHTMLNode
 */
const MotorHTMLNode = document.registerElement('motor-node', {
    prototype: Object.assign(Object.create(HTMLElement.prototype), {
        createdCallback() {
            console.log('<motor-node> createdCallback()')
            this._attached = false
            this.attachPromise = null
            this._cleanedUp = true

            this.node = this.makeNode()
            this.createChildObserver()

            this.childObserver.observe(this, { childList: true })

            // TODO: mountPromise for Node, not just Scene.
            if (this.nodeName == 'MOTOR-SCENE') {

                // XXX: "mountPromise" vs "ready":
                //
                // "ready" seems to be more intuitive on the HTML side because
                // if the user has a reference to a motor-node or a motor-scene
                // and it exists in DOM, then it is already "mounted" from the
                // HTML API perspective. Maybe we can use "mountPromise" for
                // the imperative API, and "ready" for the HTML API. For example:
                //
                // await $('motor-scene')[0].ready // When using the HTML API
                // await node.mountPromise // When using the imperative API
                //
                // Or, maybe we can just use "ready" for both cases?...
                this.mountPromise = this.node.mountPromise
                this.ready = this.mountPromise
            }
        },

        makeNode() {
            return new Node
        },

        createChildObserver() {
            this.childObserver = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    let nodes = Array.from(mutation.addedNodes)

                    nodes = nodes.filter(node => {
                        let keep = true

                        if (
                            node.nodeName.match(/^MOTOR-/)
                            || (

                                // Ignore the motorDomSceneContainer because we
                                // won't move it (that should stay in place inside
                                // of the <motor-scene> element). Other elements
                                // get moved into the scene graph (for example, if
                                // you put a <div> inside of a <motor-node>, then
                                // that <div> gets transplanted into the Motor
                                // scene graph DOM tree which is rooted in the
                                // <motor-scene>. You'll understand what this means
                                // now if you take a look in the element inspector.
                                node.className // some nodes don't have a class name (#text, #comment, #document).
                                && node.className.match(/^motorDomSceneContainer/)
                            )
                        ) {
                            keep = false
                        }

                        return keep
                    })

                    nodes.forEach(node => {

                        // this is kind of a hack: we remove the content
                        // from the motor-node in the actual DOM and put
                        // it in the node-controlled element, which may
                        // make it a little harder to debug, but at least
                        // for now it works.
                        this.node.element.element.appendChild(node)
                    })
                })
            })
        },

        async attachedCallback() {
            console.log('<motor-node> attachedCallback()')
            this._attached = true

            // If the node is currently being attached, wait for that to finish
            // before attaching again, to avoid a race condition. This will
            // almost never happen, but just in case, it'll protect against
            // naive programming on the end-user's side (f.e., if they attach
            // the motor-node element to the DOM then move it to a new element
            // within the same tick.
            await this.attachPromise

            this.attachPromise = new Promise(async (resolve) => {

                if (this._cleanedUp) {
                    this._cleanedUp = false

                    this.childObserver.observe(this, { childList: true })
                }

                // The scene doesn't have a parent to attach to.
                if (this.nodeName.toLowerCase() != 'motor-scene')
                    this.parentNode.node.addChild(this.node)

                resolve()
            })
        },

        async detachedCallback() {
            console.log('<motor-node> detachedCallback()')
            this._attached = false

            // If the node is currently being attached, wait for that to finish
            // before starting the detach process (to avoid a race condition).
            // if this.attachPromise is null, excution continues without
            // going to the next tick (TODO: is this something we can rely on
            // in the language spec?).
            if (this.attachPromise) await this.attachPromise
            this.attachPromise = null

            // XXX For performance, deferr to the next tick before cleaning up
            // in case the element is actually being re-attached somewhere else
            // within this same tick (detaching and attaching is synchronous,
            // so by deferring to the next tick we'll be able to know if the
            // element was re-attached or not in order to clean up or not), in
            // which case we want to preserve the style sheet, preserve the
            // animation frame, and keep the scene in the sceneList. {{
            await Promise.resolve() // deferr to the next tick.
            // If the scene wasn't re-attached, clean up.  TODO (performance):
            // How can we coordinate this with currently running animations so
            // that Garabage Collection doesn't make the frames stutter?
            if (!this._attached) {
                this.cleanUp()
                this._cleanedUp = true
            }
            // }}
        },

        cleanUp() {
            cancelAnimationFrame(this.rAF)
            this.childObserver.disconnect()
        },

        attributeChangedCallback(attribute, oldValue, newValue) {
            console.log('<motor-node> attributeChangedCallback()')
            this.updateNodeProperty(attribute, oldValue, newValue)
        },

        updateNodeProperty(attribute, oldValue, newValue) {
            // TODO: Handle actual values (not just string property values as
            // follows) for performance; especially when DOMMatrix is supported
            // by browsers.

            // attributes on our HTML elements are the same name as those on
            // the Node class (the setters).
            if (newValue !== oldValue) {
                if (attribute.match(/opacity/i))
                    this.node[attribute] = parseFloat(newValue)
                else if (attribute.match(/sizemode/i))
                    this.node[attribute] = parseStringArray(newValue)
                else if (
                    attribute.match(/rotation/i)
                    || attribute.match(/scale/i)
                    || attribute.match(/position/i)
                    || attribute.match(/absoluteSize/i)
                    || attribute.match(/proportionalSize/i)
                    || attribute.match(/align/i)
                    || attribute.match(/mountPoint/i)
                    || attribute.match(/origin/i) // TODO on imperative side.
                ) {
                    this.node[attribute] = parseNumberArray(newValue)
                }
                else { /* crickets */ }
            }
        },
    }),
})

export default MotorHTMLNode

function parseNumberArray(str) {
    if (!isNumberArrayString(str))
        throw new Error(`Invalid array. Must be an array of numbers of length 3.`)

    let numbers = str.split('[')[1].split(']')[0].split(',')

    numbers = numbers.map(num => window.parseFloat(num))
    return numbers
}

function parseStringArray(str) {
    let strings = str.split('[')[1].split(']')[0].split(',')
    strings = strings.map(str => str.trim())
    return strings
}

function isNumberArrayString(str) {
    return !!str.match(/^\s*\[\s*(-?((\d+\.\d+)|(\d+))(\s*,\s*)?){3}\s*\]\s*$/g)
}
import Node from '../motor/Node'
import Scene from '../motor/Scene'

import MotorHTMLNode from './node'

import jss from '../jss'

const sceneList = []
let style = null

/**
 * @class MotorHTMLScene
 * @extends MotorHTMLNode
 */
const MotorHTMLScene = document.registerElement('motor-scene', {
    prototype: Object.assign(Object.create(MotorHTMLNode.prototype), {
        createdCallback() {
            MotorHTMLNode.prototype.createdCallback.call(this)
            console.log('<motor-scene> createdCallback()')

            sceneList.push(this)

            if (!style) {
                style = jss.createStyleSheet({
                    motorSceneElement: {
                        boxSizing: 'border-box',
                        display:   'block',
                        overflow:  'hidden',
                    },
                })
            }

            style.attach()
            this.classList.add(style.classes.motorSceneElement)
        },

        makeNode() {
            return new Scene(this)
        },

        cleanUp() {
            MotorHTMLNode.prototype.cleanUp.call(this)

            sceneList.pop(this)

            // TODO: unmount the scene

            // dispose of the scene style if we no longer have any scenes
            // attached anywhere.
            // TODO (performance): Would requesting an animation frame when
            // detaching or attaching a stylesheet make things perform
            // better?
            if (sceneList.length == 0) {
                style.detach()
                style = null
            }
        },

        attributeChangedCallback(attribute, oldValue, newValue) {
            MotorHTMLNode.prototype.attributeChangedCallback.call(this)
            console.log('<motor-scene> attributeChangedCallback()')
            this.updateSceneProperty(attribute, oldValue, newValue)
        },

        updateSceneProperty(attribute, oldValue, newValue) {
            // ...
        },
    }),
})

export default MotorHTMLScene