diff options
Diffstat (limited to 'files/pt-br/web/web_components/usando_custom_elements/index.html')
-rw-r--r-- | files/pt-br/web/web_components/usando_custom_elements/index.html | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/files/pt-br/web/web_components/usando_custom_elements/index.html b/files/pt-br/web/web_components/usando_custom_elements/index.html new file mode 100644 index 0000000000..55af21ca48 --- /dev/null +++ b/files/pt-br/web/web_components/usando_custom_elements/index.html @@ -0,0 +1,289 @@ +--- +title: Usando custom elements +slug: Web/Web_Components/Usando_custom_elements +tags: + - Autonomos + - Classes + - Guide + - HTML + - Web Components + - custom elements + - customized +translation_of: Web/Web_Components/Using_custom_elements +--- +<div>{{DefaultAPISidebar("Web Components")}}</div> + +<p class="summary">Um dos principais recursos do padrão de Web Components é a capacidade de criar elementos personalizados que encapsulam sua funcionalidade em uma página HTML, em vez de ter que se contentar com um lote longo e aninhado de elementos que, juntos, fornecem um recurso de página personalizada. Este artigo apresenta o uso da API de Custom Elements.</p> + +<div class="note"> +<p><strong>Nota</strong>: Custom elements são suportados por padrão no Firefox, Chrome e Edge (76). Opera e Safari até agora suportam apenas custom elements autônomos.</p> +</div> + +<h2 id="Visão_de_alto_nível">Visão de alto nível</h2> + +<p>O controlador de custom elements em um documento da web é o objeto {{domxref("CustomElementRegistry")}} — este objeto permite que você registre um custom element na página, retorne informações sobre quais custom elements estão registrados, etc..</p> + +<p>Para registar um custom element na página, use o método {{domxref("CustomElementRegistry.define()")}}. Isso leva como argumentos:</p> + +<ul> + <li>Um {{domxref("DOMString")}} que representa o nome que você está dando ao elemento. Observe que os nomes dos custom elements <a href="https://html.spec.whatwg.org/#valid-custom-element-name">requerem o uso de um traço</a> (kebab-case); não podem ser palavras isoladas.</li> + <li>Um objeto de <a href="/en-US/docs/Web/JavaScript/Reference/Classes">classe</a> que define o comportamento do elemento.</li> + <li>Opcionalmente, um objeto de opções contendo uma propriedade <code>extends</code>, que especifica o elemento integrado do qual seu elemento herda, se houver (relevante apenas para elementos integrados personalizados; consulte a definição abaixo).</li> +</ul> + +<p>Então, por exemplo, podemos definir um custom element <a href="https://mdn.github.io/web-components-examples/word-count-web-component/">word-count</a> <code>(contagem-palavras)</code> assim:</p> + +<pre class="brush: js notranslate">customElements.define('word-count', WordCount, { extends: 'p' });</pre> + +<p>O elemento é chamado de <code>word-count</code>, seu objeto de classe é <code>WordCount</code>, e estende o elemento {{htmlelement("p")}}.</p> + +<p>O objeto de classe de um custom element é escrito usando a sintaxe de classe ES 2015. Por exemplo, <code>WordCount</code> é estruturado assim::</p> + +<pre class="brush: js notranslate">class WordCount extends HTMLParagraphElement { + constructor() { + // Sempre chame super primeiro no construtor + super(); + + // Funcionalidade do elemento escrita aqui + + ... + } +}</pre> + +<p>Este é apenas um exemplo simples, mas você pode fazer mais aqui. É possível definir retornos de chamada de ciclo de vida específicos dentro da classe, que são executados em pontos específicos do ciclo de vida do elemento. Por exemplo, <code>connectedCallback</code> é invocado cada vez que o custom element é anexado a um elemento conectado ao documento, enquanto <code>attributeChangedCallback</code> é invocado quando um dos atributos do elemento customizado é adicionado, removido ou alterado.</p> + +<p>Você aprenderá mais sobre eles na seção {{anch("Using the lifecycle callbacks")}} abaixo.</p> + +<p>Existem dois tipos de custom elements:</p> + +<ul> + <li><strong>Autonomous custom elements </strong>são autonômos — eles não herdam de elementos HTML padrão. Você os usa em uma página, literalmente escrevendo-os como um elemento HTML. Por exemplo <code><popup-info></code>, ou <code>document.createElement("popup-info")</code>.</li> + <li><strong>Customized built-in elements</strong> herdam de elementos HTML básicos. Para criar um deles, você deve especificar qual elemento eles estendem (como implícito nos exemplos acima), e eles são usados escrevendo o elemento básico, mas especificando o nome do elemento personalizado no atributo {{htmlattrxref("is")}} (ou propriedade). Por exemplo <code><p is="word-count"></code>, ou <code>document.createElement("p", { is: "word-count" })</code>.</li> +</ul> + +<h2 id="Trabalhando_com_alguns_exemplos_simples">Trabalhando com alguns exemplos simples</h2> + +<p>Neste ponto, vamos examinar mais alguns exemplos simples para mostrar como os custom elements são criados com mais detalhes.</p> + +<h3 id="Custom_elements_autônomos">Custom elements autônomos</h3> + +<p>Vamos dar uma olhada em um exemplo de um custom element autônomo — <code><a href="https://github.com/mdn/web-components-examples/tree/master/popup-info-box-web-component"><popup-info-box></a></code> (veja um <a href="https://mdn.github.io/web-components-examples/popup-info-box-web-component/">exemplo ao vivo</a>). Isso pega um imagem de ícone e uma sequência de texto e incorpora o ícone na página. Quando o ícone está em foco, ele exibe o texto em uma caixa pop-up de informações para fornecer mais informações no contexto.</p> + +<p>Para começar, o arquivo JavaScript define uma classe chamada <code>PopUpInfo</code>, que estende a classe genérica {{domxref("HTMLElement")}}.</p> + +<pre class="brush: js notranslate">class PopUpInfo extends HTMLElement { + constructor() { + // Sempre chame super primeiro no construtor + super(); + + // escreva a funcionalidade do elemento aqui + + ... + } +}</pre> + +<p>O trecho de código anterior contém a definição do <code><a href="/en-US/docs/Web/JavaScript/Reference/Classes/constructor">constructor()</a></code> da classe, que sempre começa chamando <code><a href="/en-US/docs/Web/JavaScript/Reference/Operators/super">super()</a></code> para que a cadeia de protótipo correta seja estabelecida.</p> + +<p>Dentro do construtor, definimos toda a funcionalidade que o elemento terá quando uma instância dele for instanciada. Neste caso, anexamos uma shadow root ao custom element, usamos alguma manipulação de DOM para criar a estrutura de shadow DOM interna do elemento - que é então anexada à shadow root - e, finalmente, anexamos algum CSS à shadow root para estilizá-la.</p> + +<pre class="brush: js notranslate">// Create a shadow root +this.attachShadow({mode: 'open'}); // sets and returns 'this.shadowRoot' + +// Create (nested) span elements +const wrapper = document.createElement('span'); +wrapper.setAttribute('class','wrapper'); +const icon = wrapper.appendChild(document.createElement('span')); +icon.setAttribute('class','icon'); +icon.setAttribute('tabindex', 0); +// Insert icon from defined attribute or default icon +const img = icon.appendChild(document.createElement('img')); +img.src = this.hasAttribute('img') ? this.getAttribute('img') : 'img/default.png'; + +const info = wrapper.appendChild(document.createElement('span')); +info.setAttribute('class','info'); +// Take attribute content and put it inside the info span +info.textContent = this.getAttribute('data-text'); + +// Create some CSS to apply to the shadow dom +const style = document.createElement('style'); +style.textContent = '.wrapper {' + +// CSS truncated for brevity + +// attach the created elements to the shadow DOM +this.shadowRoot.append(style,wrapper); + +</pre> + +<p>Por fim, registramos nosso custom element no <code>CustomElementRegistry</code> usando o método<code>define()</code> mencionado anteriormente — nos parâmetros especificamos o nome do elemento e, em seguida, o nome da classe que define sua funcionalidade:</p> + +<pre class="brush: js notranslate">customElements.define('popup-info', PopUpInfo);</pre> + +<p>Agora está disponível para uso em nossa página. Em nosso HTML, nós o usamos assim:</p> + +<pre class="brush: html notranslate"><popup-info img="img/alt.png" data-text="Your card validation code (CVC) + is an extra security feature — it is the last 3 or 4 numbers on the + back of your card."></popup-info></pre> + +<div class="note"> +<p><strong>Note</strong>: Você pode ver o <a href="https://github.com/mdn/web-components-examples/blob/master/popup-info-box-web-component/main.js">código-fonte JavaScript completo</a> aqui.</p> +</div> + +<h3 id="Estilos_internos_vs._externos">Estilos internos vs. externos</h3> + +<p>No exemplo acima, aplicamos o estilo ao Shadow DOM usando um elemento {{htmlelement("style")}}, mas é perfeitamente possível fazer isso referenciando uma folha de estilo externa de um elemento {{htmlelement("link")}} em vez disso.</p> + +<p>Por exemplo, dê uma olhada neste código de nosso exemplo <a href="https://mdn.github.io/web-components-examples/popup-info-box-external-stylesheet/">popup-info-box-external-stylesheet</a> (veja o <a href="https://github.com/mdn/web-components-examples/blob/master/popup-info-box-external-stylesheet/main.js">código-fonte</a>):</p> + +<pre class="brush: js notranslate">// Aplicar estilos externos ao shadow dom +const linkElem = document.createElement('link'); +linkElem.setAttribute('rel', 'stylesheet'); +linkElem.setAttribute('href', 'style.css'); + +// Anexe o elemento criado ao shadow dom +shadow.appendChild(linkElem);</pre> + +<p>Observe que os elementos {{htmlelement("link")}} não bloqueiam a pintura do shadow root, portanto, pode haver um flash de conteúdo não estilizado (FOUC) enquanto a folha de estilo é carregada.</p> + +<p>Muitos navegadores modernos implementam uma otimização para tags {{htmlelement("style")}} clonadas de um nó comum ou que tenham texto idêntico, para permitir que compartilhem uma única folha de estilo de apoio. Com essa otimização, o desempenho dos estilos externo e interno deve ser semelhante.</p> + +<h3 id="Customized_built-in_elements">Customized built-in elements</h3> + +<p>Agora vamos dar uma olhada em outro exemplo de custom element integrado — <a href="https://github.com/mdn/web-components-examples/tree/master/expanding-list-web-component">expanding-list</a> (<a href="https://mdn.github.io/web-components-examples/expanding-list-web-component/">ver ao vivo também</a>). Isso transforma qualquer lista não ordenada em um menu de expansão/recolhimento.</p> + +<p>Em primeiro lugar, definimos a classe do nosso elemento, da mesma maneira que antes:</p> + +<pre class="brush: js notranslate">class ExpandingList extends HTMLUListElement { + constructor() { + // Sempre chame super primeiro no construtor + super(); + + // escreva a funcionalidade do elemento aqui + + ... + } +}</pre> + +<p>Não explicaremos a funcionalidade do elemento em detalhes aqui, mas você pode descobrir como ele funciona verificando o código-fonte. A única diferença real aqui é que nosso elemento está estendendo a interface {{domxref("HTMLUListElement")}}, e não {{domxref("HTMLElement")}}. Portanto, ele tem todas as características de um elemento {{htmlelement("ul")}} com a funcionalidade que definimos construída no topo, ao invés de ser um elemento autônomo. Isso é o que o torna um elemento integrado personalizado, em vez de um elemento autônomo.</p> + +<p>Em seguida, registramos o elemento usando o método <code>define()</code> como antes, exceto que, desta vez, ele também inclui um objeto de opções que detalha de qual elemento nosso elemento personalizado herda:</p> + +<pre class="brush: js notranslate">customElements.define('expanding-list', ExpandingList, { extends: "ul" });</pre> + +<p>Usar o elemento integrado em um documento da web também parece um pouco diferente:</p> + +<pre class="brush: html notranslate"><ul is="expanding-list"> + + ... + +</ul></pre> + +<p>Você usa um elemento <code><ul></code> normalmente, mas especifica o nome do elemento personalizado dentro do atributo <code>is</code>.</p> + +<div class="note"> +<p><strong>Nota</strong>: Novamente, você pode ver o <a href="https://github.com/mdn/web-components-examples/blob/master/expanding-list-web-component/main.js">código-fonte JavaScript completo</a> aqui.</p> +</div> + +<h2 id="Usando_os_callbacks_do_ciclo_de_vida">Usando os callbacks do ciclo de vida</h2> + +<p>Você pode definir vários retornos de chamada diferentes dentro da definição de classe de um custom element, que disparam em diferentes pontos do ciclo de vida do elemento:</p> + +<ul> + <li><code>connectedCallback</code>: Chamado sempre que o custom element é anexado a um elemento conectado ao documento. Isso acontecerá sempre que o nó for movido e pode acontecer antes que o conteúdo do elemento tenha sido totalmente analisado. + + <div class="note"> + <p><strong>Nota</strong>: <code>connectedCallback</code> pode ser chamado assim que seu elemento não estiver mais conectado, use {{domxref("Node.isConnected")}} para ter certeza.</p> + </div> + </li> + <li><code>disconnectedCallback</code>: Invocado sempre que o custom element é desconectado do documento DOM.</li> + <li><code>adoptedCallback</code>: Invocado sempre que o custom element é movido para um novo documento.</li> + <li><code>attributeChangedCallback</code>: Invocado sempre que um dos atributos do custom element é adicionado, removido ou alterado. Os atributos a serem observados na mudança são especificados em um método estático <code>observedAttributes</code></li> +</ul> + +<p>Vejamos um exemplo em uso. O código abaixo é retirado do exemplo <a href="https://github.com/mdn/web-components-examples/tree/master/life-cycle-callbacks">life-cycle-callbacks</a> (<a href="https://mdn.github.io/web-components-examples/life-cycle-callbacks/">ver rodando ao vivo</a>). Este é um exemplo trivial que simplesmente gera um quadrado colorido de tamanho fixo na página. O custom element tem a seguinte aparência:</p> + +<pre class="brush: html notranslate"><custom-square l="100" c="red"></custom-square></pre> + +<p>O construtor da classe é realmente simples - aqui anexamos um shadow DOM ao elemento e, em seguida, anexamos os elementos vazios {{htmlelement("div")}} e {{htmlelement("style")}} ao shadow root:</p> + +<pre class="brush: js notranslate">const shadow = this.attachShadow({mode: 'open'}); + +const div = document.createElement('div'); +const style = document.createElement('style'); +shadow.appendChild(style); +shadow.appendChild(div);</pre> + +<p>A função chave neste exemplo é <code>updateStyle()</code> — isso pega um elemento, pega seu shadow root, encontra seu elemento <code><style></code>, e adiciona {{cssxref("width")}}, {{cssxref("height")}}, e {{cssxref("background-color")}} para o estilo.</p> + +<pre class="brush: js notranslate">function updateStyle(elem) { + const shadow = elem.shadowRoot; + shadow.querySelector('style').textContent = ` + div { + width: ${elem.getAttribute('l')}px; + height: ${elem.getAttribute('l')}px; + background-color: ${elem.getAttribute('c')}; + } + `; +}</pre> + +<p>As atualizações reais são todas tratadas pelos retornos de chamada do ciclo de vida, que são colocados dentro da definição da classe como métodos. O <code>connectedCallback()</code> é executado sempre que o elemento é adicionado ao DOM - aqui, executamos a função <code>updateStyle()</code> para garantir que o quadrado seja estilizado conforme definido em seus atributos:</p> + +<pre class="brush: js notranslate">connectedCallback() { + console.log('Custom square element added to page.'); + updateStyle(this); +}</pre> + +<p>Os retornos de chamada <code>disconnectedCallback()</code> e <code>adoptedCallback()</code> registram mensagens simples no console para nos informar quando o elemento é removido do DOM ou movido para uma página diferente:</p> + +<pre class="brush: js notranslate">disconnectedCallback() { + console.log('Custom square element removed from page.'); +} + +adoptedCallback() { + console.log('Custom square element moved to new page.'); +}</pre> + +<p>O <code>attributeChangedCallback()</code> é executado sempre que um dos atributos do elemento é alterado de alguma forma. Como você pode ver por suas propriedades, é possível atuar sobre os atributos individualmente, observando seus nomes e antigos e novos valores de atributos. Neste caso, entretanto, estamos apenas executando a função <code>updateStyle()</code> novamente para garantir que o estilo do quadrado seja atualizado de acordo com os novos valores:</p> + +<pre class="brush: js notranslate">attributeChangedCallback(name, oldValue, newValue) { + console.log('Custom square element attributes changed.'); + updateStyle(this); +}</pre> + +<p>Observe que, para fazer com que o retorno de chamada <code>attributeChangedCallback()</code> seja acionado quando um atributo for alterado, você deve observar os atributos. Isso é feito especificando um método <code>static get observedAttributes()</code> dentro da classe de custom element - isso deve <code>retornar</code> um array contendo os nomes dos atributos que você deseja observar:</p> + +<pre class="brush: js notranslate">static get observedAttributes() { return ['c', 'l']; }</pre> + +<p>Isso é colocado bem no topo do construtor, em nosso exemplo.</p> + +<div class="note"> +<p><strong>Nota</strong>: Encontre o <a href="https://github.com/mdn/web-components-examples/blob/master/life-cycle-callbacks/main.js">código-fonte JavaScript completo</a> aqui.</p> +</div> + +<h2 id="Polyfills_vs._classes">Polyfills vs. classes</h2> + +<p>Polyfills de Custom Element podem corrigir construtores nativos, como <code>HTMLElement</code> e outros, e retornar uma instância diferente daquela recém-criada.</p> + +<p>Se você precisar de um <code>constructor</code> e uma chamada de <code>super</code> obrigatória, lembre-se de passar argumentos opcionais e retornar o resultado de tal chamada de <code>super</code>.</p> + +<pre class="brush: js notranslate">class CustomElement extends HTMLElement { + constructor(...args) { + const self = super(...args); + // self functionality written in here + // self.addEventListener(...) + // return the right context + return self; + } +}</pre> + +<p>Se você não precisa realizar nenhuma operação no construtor, você pode simplesmente omiti-lo para que seu comportamento nativo (veja a seguir) seja preservado.</p> + +<pre class="brush: js notranslate"> constructor(...args) { return super(...args); } +</pre> + +<h2 id="Transpiladores_vs._classes">Transpiladores vs. classes</h2> + +<p>Observe que as classes ES2015 não podem ser transpiladas de forma confiável em Babel 6 ou TypeScript visando navegadores legados. Você pode usar o Babel 7 ou o <a href="https://www.npmjs.com/package/babel-plugin-transform-builtin-classes">babel-plugin-transform-builtin-classes</a> para Babel 6, e target ES2015 em TypeScript em vez do legado..</p> + +<h2 id="Bibliotecas">Bibliotecas</h2> + +<p>Existem várias bibliotecas que são construídas em Web Components com o objetivo de aumentar o nível de abstração ao criar elementos personalizados. Algumas dessas bibliotecas são <a href="https://github.com/devpunks/snuggsi" rel="nofollow">snuggsi ツ</a>, <a href="https://x-tag.github.io/" rel="nofollow">X-Tag</a>, <a href="http://slimjs.com/" rel="nofollow">Slim.js</a>, <a href="https://lit-element.polymer-project.org/">LitElement</a>, <a href="https://www.htmlelements.com/">Smart</a>, <a href="https://stenciljs.com">Stencil</a> e <a href="https://github.com/WebReflection/hyperHTML-Element">hyperHTML-Element</a>.</p> |