Quer sair do zero em ReactJS e dar seus primeiros passos com essa biblioteca de JavaScript? Você vai aprender o que precisa com esse Minicurso de ReactJS!
Ao longo desse curso, você aprenderá do básico até a construção de um projeto completo em ReactJS.
O objetivo é que você consiga dar seus primeiros passos com essa ferramenta, entenda seu funcionamento, conceitos mais importantes, e seja capaz de construir um Player de Audioobook totalmente funcional como o Audible da Amazon.
Conteúdo do Minicurso:
Minicurso de ReactJS – Aula 1 – Apresentação do Projeto e Primeiros Passos
Vamos dar início ao Minicurso de ReactJS, onde você poderá sair do zero e dar seus primeiros passos com essa incrível ferramenta. Utilizando seus conhecimentos em HTML, CSS e JavaScript, você aprenderá a criar um projeto completo.
Caso prefira esse conteúdo no formato de vídeo-aula, assista ao vídeo abaixo ou acesse o nosso canal do YouTube!
Para receber o(s) arquivo(s) utilizados na aula, preencha seu e-mail:
O que é ReactJs?
O ReactJS é uma tecnologia do JavaScript, então para que você entenda o funcionamento dele é importane que você tenha um certo conhecimento em HTML, CSS e JavaScript!
O objetivo do ReactJS é te permitir criar um front-end de uma forma mais visual e mais dinâmica para que você consiga criar sua interface para seu site ou aplicativo.
Vamos conseguir utilizar algumas ferramentas bem interessantes com o ReactJS para criar esses layouts que são mais visiveis, tanto que o nosso projeto completo com ReactJS será a criação de um tocador de audiobook.
Nós vamos criar uma réplica do audible da Amazon. Dá só uma olhada como ele vai ficar utilizando essa tecnologia!
Vou deixar aqui alguns minicursos que podem te ajudar caso você ainda não tenha nenhum conhecimento em HTML, CSS e JavaScript:
O que Vamos Precisar?
Além desses conhecimentos é importante que você tenha instalado no seu computador o NodeJS. Vou deixar aqui tanto o link da nossa publicação de instalação quanto do minicurso de NodeJS que nós temos para te ajudar:
- https://www.hashtagtreinamentos.com/como-instalar-o-nodejs-javascript
- https://www.hashtagtreinamentos.com/minicurso-de-nodejs-javascript
E durante as aulas, nós vamos utilizar o Visual Studio Code, então é interessante que você tenha o mesmo editor de código para que consiga seguir o passo a passo exatamente como vamos mostrar.
É claro que se você já está acostumado com outro editor de código, pode utilizar, mas a ideia é que utilize o mesmo para que consiga acompanhar exatamente o passo a passo que vamos te mostrar.
Também já temos aqui no nosso blog uma publicação mostrando todo o passo a passo de instalação do VS Code, então se ainda não tem instalado dá uma olhada lá e volta aqui:
Projeto Completo com ReactJS
Depois das informações importantes nós podemos começar o nosso projeto. O primeiro passo é abrirmos a pasta onde vamos criar o nosso projeto e selecionar a opção Abrir no Terminal ou Arbir janela do PowerShell aqui.
Já dentro do terminal, que já está com o caminho da pasta onde você vai criar o projeto, vamos utilizar o comando npm create vite@latest.
Ao inserir esse código, você vai notar que o terminal vai te pedir algumas informações. Vai pedir o nome do projeto e qual a tecnologia que você vai utilizar para esse projeto.
Feito isso você vai escolher a opção de JavaScript, que é a variante, então no final você vai ter o nome do projeto, o framework e a variant.
Com isso vamos ter a base do nosso projeto e você vai notar que na pasta que selecionou já vamos ter a pasta do projeto (com o nome que você deu) e dentro dela já teremos alguns arquivos.
Vamos voltar ao terminal só para escrever os códigos que o próprio terminal está pedindo. Ele vai pedir para rodarmos 3 códigos: cd hashtag-books, npm install e npm run dev.
Nessa parte nós vamos instalar alguns códigos que precisam estar no nosso projeto para o Vite rodar o nosso projeto. Com isso nós temos a base de um projeto em ReactJS.
Após escrever todos os 3 códigos, você vai notar que terá um link para visualizar o seu projeto. Você pode ou copiar esse link ou clicar nele direto para abrir o seu projeto no navegador.
O que você vai ver é a base de um projeto em ReactJS, não é o que vamos fazer, mas é a base que precisamos para criar o nosso projeto. Repare que essa página tem até um contador para te mostrar que temos uma inteligência por trás disso!
Agora que já temos a base do projeto criado, podemos abrir o Visual Studio Code (VS Code) e dentro dele abrir o nosso projeto.
Para isso basta ir em File > Open Folder ou Arquivo > Abrir Pasta. Depois é só selecionar a pasta do projeto que acabamos de criar!
Obs.: É importante que você não feche o terminal, pois é lá que o Vite está rodando e vai permitir com que você visualize o seu projeto.
Para essa parte nós vamos utilizar o arquivo chamado App.css que é um arquivo CSS que está disponível para download, dessa forma você não vai precisar escrever nada.
Nesse caso você pode apagar o conteúdo do arquivo do Vite e colar o conteúdo desse ou simplesmente excluir o arquivo do Vite e já colocar esse no lugar.
Agora nós temos outro arquivo, chamado App.jsx que é onde vamos ter a base do nosso projeto, então você pode apagar o conteúdo desse arquivo, pois vamos escrever tudo do zero aqui dentro.
O seu código dentro do arquivo App.jsx deve ficar dessa maneira:
import './App.css'; function App() { return <></> } export default App;
Estrutura do Projeto
A estrutura do projeto vai ser o local onde nós vamos criar o código para de fato criar o nosso player de audiobook, aqui vale ressaltar que estamos trabalhando com arquivos .jsx que são arquivos de JavaScript.
Mas qual a diferença desses arquivos? Eles permitem com que você junte o HTML com JavaScript, então você pode colocar a estrutura e a inteligência da página em um único lugar.
Vamos chamar esses pedaços de códigos de componentes para compor o nosso projeto!
Antes de dar início aos componentes, nós vamos criar uma variável com as informações do livro para que seja fácil de acessar essas informações quando for necessário.
Além disso, nós vamos inserir a imagem da capa, só que para essa parte da imagem nós vamos fazer alguns ajustes, pois vamos inserir um código de JavaScript dentro do HTML, então dentro da nossa tag de imagem ao invés de colocar diretamente onde está o arquivo.
Nós vamos utilizar a variável que foi criada dentro de { }, dessa forma já vamos conseguir não só colocar o JavaScript dentro do HTML, como vamos fazer a imagem aparecer na nossa página.
import './App.css'; import brasCubasImg from './assets/bras_cubas.jpeg' function App () { const informaçcoesLivro = { nome: 'Memórias Póstumas de Brás Cubas', autor: 'Machado de Assis', totalCapitulos: 2, capa: '' }; return <> <img src= {brasCubasImg} alt='Capa do Livro' /> </> } export default App;
Nós fizemos a importação das informações da imagem para a variável brasCubasImg pegando da própria imagem, assim conseguimos visualizar essa imagem dentro do nosso projeto!
Então sempre que quiser colocar uma imagem dentro do seu projeto no ReactJS você vai precisar importar essa imagem para uma variável e utilizá-la como JavaScritp dentro do seu HTML!
import './App.css'; import brasCubasImg from './assets/bras_cubas.jpeg' function App () { const informaçcoesLivro = { nome: 'Memórias Póstumas de Brás Cubas', autor: 'Machado de Assis', totalCapitulos: 2, capa: brasCubasImg, textoAlternativo: 'Capa do livro Memórias Póstumas de Brás Cubas.' }; return <></> } export default App;
Aqui temos o ajuste das informações do livro, pois não vamos inserir a imagem da capa diretamente, a ideia foi te mostrar como você faria para utilizar uma imagem e um código JavaScript dentro do HTML.
Veja que além das informações do livro, nós colocamos um textoAlternativo. Esse texto é muito importante e você deve ter notado que quando criamos a tag de imagem nós temos o alt.
Esse alt ou texto alternativo, nada mais é do que uma informação que vai aparecer para o usuário caso aquela imagem não seja carregada ou até mesmo para pessoas que possuem algum tipo deficiência visual.
Existem alguns aplicativos que essas pessoas usam para ler uma página por exemplo, só que uma imagem não consegue ser lida como um texto. É aí que entra o texto alternativo, pois ele vem como uma descrição da imagem.
Então não vai servir apenas para quando não conseguir carregar a imagem, mas vai auxiliar na hora de informar qual é a imagem que deveria aparecer!
Componentes do Projeto
Agora que você já entendeu como funciona a estrutura do projeto vamos partir para a criação dos componentes do projeto.
O primeiro componente que nós vamos criar é da capa, então você pode clicar com o botão direito em src e depois em new file (ou novo arquivo). O nome desse arquivo vai ser Capa.jsx.
Esse componente vai ser a inteligência necessária para que você tenha a capa do livro sendo exibida da forma correta!
Nesse arquivo nós vamos ter a função Capa que vai ter o objetivo de mostrar a imagem da capa na página, só que a ideia é que possamos receber algumas informações do nosso código principal para alimentar essa função e retornar com a informação adequada.
Com isso nós vamos receber tanto a imagem quanto o texto alternativo e vamos retornar isso na forma de imagem, dessa forma a imagem fica visível dentro da página.
function Capa (props) { return <img className='capa' src={props.imagemCapa} alt={props.textoAlternativo} />; } export default Capa;
Obs.: Você deve ter notado que temos um className nesse código, que nada mais é do que a classe dessa imagem, mas nesse caso temos que utilizar esse nome, pois class já é utilizado dentro do CSS para fazer essa referência de classe. Essa className=’capa’ é a classe da nossa capa e já existe dentro do arquivo de CSS que disponibilizamos. Isso é importante, pois se visualizar essa imagem sem a classe, vai notar que ela vai ocupar um espaço muito grande, e com a classe, que já está ajustada, já vai ficar do tamanho correto para o nosso projeto.
Obs.2: Dentro da função, você notou que temos props, essas props são as informações que vamos passar para um coponente (passamos no código App.jsx e usamos no Capa.jsx). Na hora de usar vamos colocar props. + o nome da informação que queremos, pois estão todas em um único lugar.
Além disso, temos mais alguns ajustes no código principal. Onde vamos ter a chamada dessa função Capa e a importação dela (por isso do export default Capa) no final do código.
Esse código final permite com que você possa chamar esse código em outros códigos que você tenha, dessa forma podemos deixar tudo mais organizado e separado nos componentes ao invés de deixar um bloco gigante de código com todas as informações.
import './App.css'; import brasCubasImg from './assets/bras_cubas.jpeg' import Capa from './Capa'; function App () { const informaçcoesLivro = { nome: 'Memórias Póstumas de Brás Cubas', autor: 'Machado de Assis', totalCapitulos: 2, capa: '' }; return <> <Capa imagemCapa={informacoesLivro.capa} textoAlternativo={informacoesLivro.textoAlternativo} /> </> } export default App;
Minicurso de ReactJS – Aula 2 – Conceito mais Importante do ReactJS
Vamos para a segunda aula do Minicurso de ReactJS. Nesta aula, iremos construir a seção do Seletor de Capítulos e os Botões de Controle. Além disso, falaremos sobre o conceito mais importante do ReactJS: a variável de estado.
Caso prefira esse conteúdo no formato de vídeo-aula, assista ao vídeo abaixo ou acesse o nosso canal do YouTube!
Para receber o(s) arquivo(s) utilizados na aula, preencha seu e-mail:
Na segunda aula do nosso Minicurso de ReactJS, daremos continuidade ao projeto do nosso player de audiobook. Nela construiremos a seção do Seletor de Capítulos e os Botões de Controle do player.
Além disso, veremos um dos conceitos mais importantes do ReactJS, que é a variável de estado. É ela que nos permitirá fazer a transição do botão de Play para o botão de Pause.
Então, abra o seu projeto que começamos a criar na aula passada e vamos dar continuidade a ele!
Identificando as Seções – O que Iremos Construir
Na primeira aula do nosso minicurso, construímos a base do player de audiobook com a imagem de capa do livro.
Hoje, começaremos o desenvolvimento de outras três seções que compõem esse player: o seletor de capítulos, o progresso na leitura e os botões de controle.
Cada uma dessas seções de interface será um componente separado, que depois integraremos ao componente principal (App.jsx).
Inicializando o Projeto
Com o projeto aberto, acesse o terminal do VS Code e execute o comando npm run dev.
Obs.: Certifique-se de que o VS Code esteja olhando para a pasta raiz do projeto. Caso não esteja, navegue até ela utilizando o comando cd hashtag-books (se a pasta do seu projeto tiver outro nome, troque hashtag-books pelo nome correspondente).
Feito isso, o projeto será aberto no localhost do seu computador na porta especificada.
Qualquer alteração que fizermos no nosso código será refletida nessa página.
Componentes do Projeto
Até o momento, o único componente criado para o nosso projeto foi o da Capa, responsável por exibir a capa do livro no nosso player de audiobook.
Além dele, criaremos mais três componentes, um para cada uma das novas seções que iremos desenvolver.
Respeitando a estrutura de pastas predefinidas no projeto pelo Vite, vamos criar três arquivos .jsx dentro da pasta src: BotoesControle.jsx, ContainerProgresso.jsx e SeletorCapitulos.jsx.
Bootstrap Icons – Biblioteca de Ícones
Dentro da nossa aplicação, teremos ícones para representar cada um dos botões presentes nela.
Para termos ícones visualmente atrativos e profissionais, vamos utilizar a biblioteca Bootstrap Icons. Ela nos oferece uma ampla variedade de ícones com design profissional desenvolvidos por especialistas.
Esses ícones são personalizáveis e podem ser facilmente utilizados e incorporados ao seu projeto para melhorar a aparência e usabilidade dele. Você pode acessar a página oficial da biblioteca aqui:
Para instalá-la, primeiro vamos finalizar a execução da nossa aplicação. Abra o terminal no VS Code, pressione Ctrl + C, digite S e aperte Enter.
Feito isso, copie o código fornecido na página inicial da biblioteca Bootstrap Icons e execute-o no terminal do VS Code.
Código para instalação do Bootstrap Icons: npm i bootstrap-icons
Podemos verificar a disponibilidade da biblioteca dentro do arquivo package.json, indicando que a instalação foi bem-sucedida.
Depois disso, podemos executar novamente a aplicação com o comando npm run dev.
Para utilizar os ícones dessa biblioteca, basta buscar pelo estilo de ícone desejado na página oficial e copiar o código presente em Icon font.
Além disso, dentro do arquivo App.jsx, precisamos importar o CSS da biblioteca Bootstrap.
import './App.css'; import 'bootstrap-icons/font/bootstrap-icons.css'; import brasCubasImg from './assets/bras_cubas.jpeg'; import Capa from './Capa';
Seletor de Capítulos
Agora que você já instalou a biblioteca do Bootstrap Icons, podemos partir para a criação dos componentes do projeto. Começaremos pelo Seletor de Capítulos, no arquivo SeletorCapitulos.jsx.
Dentro dele, definiremos a função de mesmo nome, que receberá algumas informações do código principal (props) como argumento e retornará o botão renderizado do Seletor de Capítulos.
Para criar esse botão, utilizaremos a tag <button> com a classe chamada seletor, para que possamos aplicar as estilizações presentes no arquivo CSS a esse botão.
Dentro desse botão, teremos um elemento <i>, que é o ícone da biblioteca Bootstrap Icons que iremos utilizar para o seletor do capítulo.
Além disso, temos um parágrafo (<p>) que exibe o texto “Capítulo” seguido do valor props.capituloAtual, inserindo dinamicamente o número do capítulo atual. Para fazer isso, utilizamos as strings interpoladas do JavaScript, que nos permitem utilizar um valor dinâmico dentro do texto.
Por fim, exportamos esse componente como padrão, permitindo que seja importado e usado em outros arquivos do projeto.
function SeletorCapitulos(props) { return ( <button className='seletor'> <i className='bi bi-list-task'></i> <p>{`Capítulo ${props.capituloAtual}`}</p> </button> ); } export default SeletorCapitulos;
Obs.: Repare que foi necessário alterar o nome da propriedade class para className no código copiado da página do Bootstrap, já que estamos trabalhando com React.
Feito isso, dentro do arquivo App.jsx, vamos importar o SeletorCapitulos e adicionar esse componente ao nosso aplicativo.
import './App.css'; import 'bootstrap-icons/font/bootstrap-icons.css'; import brasCubasImg from './assets/bras_cubas.jpeg'; import Capa from './Capa'; import SeletorCapitulos from './SeletorCapitulos'; function App() { const informacoesLivro = { nome: 'Memórias Póstumas de Brás Cubas', autor: 'Machado de Assis', totalCapitulos: 2, capa: brasCubasImg, textoAlternativo: 'Capa do livro Memórias Póstumas de Brás Cubas.', }; return ( <> <Capa imagemCapa={informacoesLivro.capa} textoAlternativo={informacoesLivro.textoAlternativo} /> <SeletorCapitulos capituloAtual={1} /> </> ); } export default App;
Observe que, por enquanto, vamos definir o capituloAtual como sendo 1. Dessa forma, nossa aplicação ficará assim:
Botões de Controle
Agora que já implementamos o Seletor de Capítulos, vamos avançar para a criação dos botões de controle do nosso aplicativo.
Para isso, vamos abrir o arquivo BotoesControle.jsx e, para que você conheça outras abordagens ao criar um componente, utilizaremos uma arrow function para definir nossos botões.
Essa função receberá as informações (props) do nosso aplicativo e retornará a estrutura dos botões renderizados.
Dentro dela, teremos uma tag <div> com a classe caixa-botoes para agrupar nossos botões de controle, aplicando as estilizações necessárias a esse contêiner.
Nessa <div>, iremos definir cinco botões, cada um representando uma ação diferente: botão de recomeço, botão de retroceder, botão de Play/Pause, botão de avançar e botão de pular para o final.
Por fim, exportamos esse componente como padrão para que ele possa ser utilizado no restante do nosso projeto.
const BotoesControle = (props) => { return ( <div className="caixa-botoes"> <button> <i className="bi bi-skip-start"></i> </button> <button> <i className="bi bi-arrow-counterclockwise"></i> </button> <button> <i className="bi bi-play-circle-fill"></i> </button> <button> <i className="bi bi-arrow-clockwise"></i> </button> <button> <i className="bi bi-skip-end"></i> </button> </div> ); }; export default BotoesControle;
Obs.: Todos os códigos dos ícones foram obtidos através da página da biblioteca Bootstrap.
Feito isso, vamos importar esse componente dentro do arquivo App.jsx e adicioná-lo ao nosso aplicativo.
import './App.css'; import 'bootstrap-icons/font/bootstrap-icons.css'; import brasCubasImg from './assets/bras_cubas.jpeg'; import Capa from './Capa'; import SeletorCapitulos from './SeletorCapitulos'; import BotoesControle from './BotoesControle'; function App() { const [taTocando, definirTaTocando] = useState(false); const informacoesLivro = { nome: 'Memórias Póstumas de Brás Cubas', autor: 'Machado de Assis', totalCapitulos: 2, capa: brasCubasImg, textoAlternativo: 'Capa do livro Memórias Póstumas de Brás Cubas.', }; return ( <> <Capa imagemCapa={informacoesLivro.capa} textoAlternativo={informacoesLivro.textoAlternativo} /> <SeletorCapitulos capituloAtual={1} /> <BotoesControle /> </> ); } export default App;
Visualizando a página do nosso projeto, teremos os botões sendo exibidos logo abaixo do seletor de capítulos.
Porém, o nosso botão de Play/Pause precisa ser um botão dinâmico em que seu ícone irá alternar entre play e pause, dependendo do estado da reprodução.
Botão de Play/Pause – JavaScript
Para tornar o botão de Play/Pause em um botão dinâmico, precisamos utilizar novamente as strings interpoladas do JavaScript. Dessa forma, podemos definir a className desse botão de acordo com o estado da propriedade taTocando.
Para isso, vamos utilizar um operador ternário que irá verificar se props.taTocando é verdadeiro ou falso. Se for verdadeiro, o ícone de pausa será exibido; caso contrário, o ícone de play será mostrado.
const BotoesControle = (props) => { return ( <div className='caixa-botoes'> <button> <i className='bi bi-skip-start'></i> </button> <button> <i className='bi bi-arrow-counterclockwise'></i> </button> <button> <i className={`bi bi-${ props.taTocando ? 'pause' : 'play' }-circle-fill`}></i> </button> <button> <i className='bi bi-arrow-clockwise'></i> </button> <button> <i className='bi bi-skip-end'></i> </button> </div> ); }; export default BotoesControle;
Porém, não basta adicionar essa verificação dentro do botão. Precisamos criar a inteligência que definirá o estado da variável taTocando.
Variável de Estado – Conceito mais Importante do ReactJS
No React, utilizamos as variáveis de estado para controlar e armazenar informações que podem mudar ao longo do tempo e que têm essas mudanças refletidas na interface do usuário.
Esse é um dos conceitos mais importantes do React, reagir às variáveis de estado de forma automática.
No código do nosso aplicativo, App.jsx, vamos usar uma variável de estado para gerenciar se o audiobook está tocando ou não. Para isso, usaremos o Hook useState do React.
Os hooks são funções que permitem que você conecte funcionalidades do React com os componentes. Eles permitem que componentes funcionais tenham acesso a mais recursos, como o estado.
O useState que utilizaremos é um dos hooks mais comuns e permite adicionar o estado a um componente. Para utilizá-lo, primeiro precisamos importá-lo dentro do arquivo App.jsx.
import { useState } from 'react'; import './App.css'; import 'bootstrap-icons/font/bootstrap-icons.css'; import brasCubasImg from './assets/bras_cubas.jpeg'; import Capa from './Capa'; import SeletorCapitulos from './SeletorCapitulos'; import BotoesControle from './BotoesControle';
Feito isso, podemos definir a variável de estado chamada taTocando e atribuir uma função para atualizá-la chamada definirTaTocando.
Além disso, com o useState, vamos definir o estado inicial de taTocando como false, o que indica que o audiobook não está sendo reproduzido quando o aplicativo é carregado pela primeira vez.
import { useState } from 'react'; import './App.css'; import 'bootstrap-icons/font/bootstrap-icons.css'; import brasCubasImg from './assets/bras_cubas.jpeg'; import Capa from './Capa'; import SeletorCapitulos from './SeletorCapitulos'; import BotoesControle from './BotoesControle'; function App() { const [taTocando, definirTaTocando] = useState(false); const informacoesLivro = { nome: 'Memórias Póstumas de Brás Cubas', autor: 'Machado de Assis', totalCapitulos: 2, capa: brasCubasImg, textoAlternativo: 'Capa do livro Memórias Póstumas de Brás Cubas.', }; return ( <> <Capa imagemCapa={informacoesLivro.capa} textoAlternativo={informacoesLivro.textoAlternativo} /> <SeletorCapitulos capituloAtual={1} /> <BotoesControle taTocando={taTocando} definirTaTocando={definirTaTocando} /> </> ); } export default App;
O estado dessa variável funcionará da seguinte forma: quando o usuário clicar no botão de play/pause, chamaremos a função definirTaTocando para alternar o valor da variável taTocando entre true e false.
Para isso, precisamos atualizar nosso componente BotoesControle, dentro do arquivo BotoesControle.jsx.
Nesse componente, utilizaremos a função definirTaTocando dentro do evento onClick dos botões para alterar o estado de taTocando quando o botão de play ou pause for pressionado.
const BotoesControle = (props) => { return ( <div className="caixa-botoes"> <button> <i className="bi bi-skip-start"></i> </button> <button> <i className="bi bi-arrow-counterclockwise"></i> </button> <button onClick={() => props.definirTaTocando(true)}> <i className={`bi bi-${props.taTocando ? "pause" : "play"}-circle-fill`} ></i> </button> <button> <i className="bi bi-arrow-clockwise"></i> </button> <button> <i className="bi bi-skip-end"></i> </button> </div> ); }; export default BotoesControle;
Dessa forma, o botão Play/Pause será responsável por alternar entre os estados da variável taTocando quando for clicado e chamar a função definirTaTocando. Assim, a classe do ícone será definida dinamicamente como havíamos determinado.
Por enquanto, nosso botão só está indo de Play para Pause porque estipulamos que a função definirTaTocando só irá alterar para true o valor da variável taTocando, mas nas próximas aulas nós iremos refinar essa mudança de estado.
Minicurso de ReactJS – Aula 3 – Funções de Play e Pause no Audiobook
Chegamos à terceira aula do nosso minicurso de ReactJS! Nesta aula, iremos desenvolver as funções de Play e Pause do nosso audiobook.
Caso prefira esse conteúdo no formato de vídeo-aula, assista ao vídeo abaixo ou acesse o nosso canal do YouTube!
Para receber o(s) arquivo(s) utilizados na aula, preencha seu e-mail:
Na aula de hoje, vou te mostrar como criar as funções para os botões de Play e Pause, de forma que eles possam alternar entre si, permitindo tocar e pausar o audiobook.
Vamos reutilizar alguns conteúdos das aulas anteriores, mas também introduziremos conceitos novos, como a tag de áudio no HTML e o hook useRef do React, que nos permite criar uma referência a um elemento.
Ao final desta aula, nosso player de audiobook terá os botões de Play e Pause funcionais, sendo capaz de iniciar e interromper a reprodução do audiobook.
Funções para o Botão Play/Pause
Ao final da aula passada, definimos a variável de estado taTocando, que tem seu estado inicial definido como false e que, após chamarmos a função definirTaTocando, alterna o valor para true.
Agora, iremos construir a lógica para que o botão de fato alterne entre os estados da variável taTocando e mude a classe do ícone dinamicamente, fazendo com que o botão mude de acordo com o estado definido.
Para isso, dentro do nosso componente App, no arquivo App.jsx, vamos construir a função tocarFaixa, que chamará a função definirTaTocando e atribuirá o estado como true, indicando que a faixa passará a ser reproduzida.
Do mesmo modo, vamos definir a função pausarFaixa, que alterará o estado para false, pausando assim a reprodução.
Na última aula, vimos que podemos atribuir uma função ao clique de um botão. No entanto, ambas as funções tocarFaixa e pausarFaixa precisam ser atribuídas ao mesmo botão.
Para controlarmos isso, vamos criar uma terceira função que irá verificar a condição de estado da variável taTocando e, dependendo do resultado, definirá como o botão de Play/Pause deve se comportar.
Ou seja, se taTocando for verdadeiro, o botão deverá se comportar como um botão de Pause, chamando a função pausarFaixa. Caso contrário, deverá se comportar como um botão de Play e chamar a função tocarFaixa.
import { useState } from "react"; import "./App.css"; import "bootstrap-icons/font/bootstrap-icons.css"; import brasCubasImg from "./assets/bras_cubas.jpeg"; import Capa from "./Capa"; import SeletorCapitulos from "./SeletorCapitulos"; import BotoesControle from "./BotoesControle"; function App() { const [taTocando, definirTaTocando] = useState(false); const informacoesLivro = { nome: "Memórias Póstumas de Brás Cubas", autor: "Machado de Assis", totalCapitulos: 2, capa: brasCubasImg, textoAlternativo: "Capa do livro Memórias Póstumas de Brás Cubas.", }; const tocarFaixa = () => { definirTaTocando(true); }; const pausarFaixa = () => { definirTaTocando(false); }; const tocarOuPausarFaixa = () => { if (taTocando) { pausarFaixa(); } else { tocarFaixa(); } };
Perceba que, para criar a estrutura da função tocarOuPausarFaixa, utilizamos estruturas condicionais. Para saber mais sobre como usar estruturas condicionais em JavaScript, confira a nossa aula abaixo:
Com a função criada, vá até o return do nosso componente App() e substitua a função definirTaTocando pela tocarOuPausarFaixa dentro de BotoesControle.
return ( <> <Capa imagemCapa={informacoesLivro.capa} textoAlternativo={informacoesLivro.textoAlternativo} /> <SeletorCapitulos capituloAtual={1} /> <BotoesControle taTocando={taTocando} tocarOuPausarFaixa={tocarOuPausarFaixa} /> </> ); } export default App;
Feito isso, dentro do arquivo BotoesControle.jsx, vamos substituir a chamada da função definirTaTocando no clique do botão por uma referência à função tocarOuPausarFaixa que acabamos de criar.
Ou seja, ao invés de criar uma nova função anônima que chama e executa definirTaTocando a cada clique no botão, agora passaremos diretamente a referência da função tocarOuPausarFaixa.
const BotoesControle = (props) => { return ( <div className="caixa-botoes"> <button> <i className="bi bi-skip-start"></i> </button> <button> <i className="bi bi-arrow-counterclockwise"></i> </button> <button onClick={props.tocarOuPausarFaixa}> <i className={`bi bi-${props.taTocando ? "pause" : "play"}-circle-fill`} ></i> </button> <button> <i className="bi bi-arrow-clockwise"></i> </button> <button> <i className="bi bi-skip-end"></i> </button> </div> ); }; export default BotoesControle;
Com isso, o ícone do botão Play/Pause será alternado corretamente conforme clicarmos nele.
Gerenciador de Faixas
Com as funções que controlam o botão de Play/Pause definidas e implementadas, vamos avançar para a criação do gerenciador de faixas do nosso aplicativo.
Crie um novo arquivo dentro da pasta src chamado GerenciadorFaixas.jsx. Esse será o componente responsável por supervisionar as faixas reproduzidas do nosso audiobook.
Nesse arquivo, teremos a função GerenciadorFaixa, que terá como objetivo gerenciar as faixas desejadas do nosso aplicativo, possibilitando incorporar e controlar faixas de áudio nele.
Para isso, essa função retornará a tag <audio> do HTML. Essa tag é utilizada para incorporar conteúdo de áudio em uma página web. É através dela que adicionaremos nossos arquivos de áudio à aplicação.
Essa tag recebe um atributo muito importante que é o src (source). Esse atributo determina o caminho do arquivo de áudio que será reproduzido.
const GerenciadorFaixa = ({ faixa, referencia }) => { return <audio/>; }; export default GerenciadorFaixa;
Por enquanto, deixaremos nossa tag sem esse atributo, pois precisamos primeiro criar a pasta com as faixas de áudio do nosso livro.
Pasta com os Capítulos do Livro – Faixas de Áudio
Para armazenar e organizar as faixas de áudio do nosso audiobook, dentro da pasta assets, crie uma pasta chamada capitulos.
No material disponível para download, você encontrará dentro dessa mesma pasta no gabarito, dois arquivos de áudio, representando o primeiro e o segundo capítulo do audiobook. Copie esses áudios para dentro da pasta capitulos do seu projeto.
Feito isso, dentro dessa pasta, vamos criar um arquivo adicional chamado livro.js.
Esse arquivo será responsável por coletar e exportar os áudios para os demais arquivos do projeto. Para isso, importe os áudios disponíveis e armazene-os em um array JavaScript.
import faixa1 from './01.mp3'; import faixa2 from './02.mp3'; const livro = [faixa1, faixa2]; export default livro;
Com isso, podemos importar o nosso livro contendo as faixas de áudio para o aplicativo, no arquivo App.jsx.
import { useState } from "react"; import "./App.css"; import "bootstrap-icons/font/bootstrap-icons.css"; import brasCubasImg from "./assets/bras_cubas.jpeg"; import Capa from "./Capa"; import SeletorCapitulos from "./SeletorCapitulos"; import BotoesControle from "./BotoesControle"; import livro from './assets/capitulos/livro';
Em seguida, podemos adicionar ao nosso objeto informacoesLivro a propriedade capitulos que receberá o conteúdo da variável livro.
function App() { const [taTocando, definirTaTocando] = useState(false); const informacoesLivro = { nome: "Memórias Póstumas de Brás Cubas", autor: "Machado de Assis", totalCapitulos: 2, capa: brasCubasImg, capitulos: livro, textoAlternativo: "Capa do livro Memórias Póstumas de Brás Cubas.", };
Variável de Estado – Definir Faixa Atual
Tendo o livro e as faixas de áudio criadas e organizadas, vamos criar uma segunda variável de estado dentro do nosso App.
Essa variável será a faixaAtual, juntamente com a função definirFaixaAtual, e terá seu valor de estado inicial definido como 0.
Isso porque, como os capítulos do nosso livro estão contidos em um array JavaScript, o índice correspondente ao primeiro capítulo é o índice 0.
function App() { const [taTocando, definirTaTocando] = useState(false); const [faixaAtual, definirFaixaAtual] = useState(0);
Feito isso, vamos abrir o arquivo GerenciadorFaixa.jsx e definir que o componente GerenciadorFaixa receberá um parâmetro faixa que será o atributo src da tag <audio>.
const GerenciadorFaixa = ({ faixa }) => { return <audio src={faixa}/>; }; export default GerenciadorFaixa;
De volta ao arquivo App.jsx, vamos importar o GerenciadorFaixa.
import { useState } from "react"; import "./App.css"; import "bootstrap-icons/font/bootstrap-icons.css"; import brasCubasImg from "./assets/bras_cubas.jpeg"; import Capa from "./Capa"; import SeletorCapitulos from "./SeletorCapitulos"; import BotoesControle from "./BotoesControle"; import livro from "./assets/capitulos/livro"; import GerenciadorFaixa from './GerenciadorFaixa';
Além disso, iremos adicioná-lo ao return do App, passando para ele a propriedade faixa, que será definida a partir do objeto informacoesLivro e terá seu capítulo determinado pela variável de estado faixaAtual.
Também podemos utilizar a faixaAtual dentro do SeletorCapitulos para tornar a informação do capítulo atual dinâmica. Como faixaAtual representa o índice, em capituloAtual podemos definir o valor como sendo faixaAtual + 1.
return ( <> <Capa imagemCapa={informacoesLivro.capa} textoAlternativo={informacoesLivro.textoAlternativo} /> <SeletorCapitulos capituloAtual={faixaAtual + 1} /> <GerenciadorFaixa faixa={informacoesLivro.capitulos[faixaAtual]} /> <BotoesControle taTocando={taTocando} tocarOuPausarFaixa={tocarOuPausarFaixa} /> </> );
Criar Referência para Variável – Hook useRef
Apesar de já termos as faixas de áudio definidas e o componente GerenciadorFaixa criado, ao clicarmos no botão de Play, o audiobook ainda não é reproduzido.
Isso acontece porque, para iniciar ou pausar um elemento de mídia como a tag <audio>, precisamos interagir diretamente com ele, fazendo uma referência à própria tag.
Para construir essa referência ao elemento <audio>, utilizaremos o hook useRef do React. Esse hook nos permite acessar a referência de um elemento diretamente.
Dessa forma, podemos criar uma referência à nossa tag <audio> e realizar chamadas diretas aos métodos dela.
Vamos voltar ao App.jsx, importar esse hook e criar uma referência à nossa tag de áudio, que será inicializada com o valor null.
import { useState, useRef } from 'react'; import './App.css'; import 'bootstrap-icons/font/bootstrap-icons.css'; import brasCubasImg from './assets/bras_cubas.jpeg'; import Capa from './Capa'; import SeletorCapitulos from './SeletorCapitulos'; import BotoesControle from './BotoesControle'; import livro from './assets/capitulos/livro'; import GerenciadorFaixa from './GerenciadorFaixa'; function App() { const [taTocando, definirTaTocando] = useState(false); const [faixaAtual, definirFaixaAtual] = useState(0); const tagAudio = useRef(null);
A partir daqui, vamos usar essa referência dentro das funções tocarFaixa e pausarFaixa para acessar a propriedade current e manipular o elemento <audio>, chamando os métodos play() e pause() dele.
const tocarFaixa = () => { tagAudio.current.play(); definirTaTocando(true); }; const pausarFaixa = () => { tagAudio.current.pause(); definirTaTocando(false); };
Feito isso, vamos passar a referência tagAudio como uma prop chamada referencia para o componente GerenciadorFaixa dentro do return.
return ( <> <Capa imagemCapa={informacoesLivro.capa} textoAlternativo={informacoesLivro.textoAlternativo} /> <SeletorCapitulos capituloAtual={faixaAtual + 1} /> <GerenciadorFaixa faixa={informacoesLivro.capitulos[faixaAtual]} referencia={tagAudio} /> <BotoesControle taTocando={taTocando} tocarOuPausarFaixa={tocarOuPausarFaixa} /> </> );
Por fim, dentro do GerenciadorFaixa.jsx, vamos adicionar nossa referência ao componente GerenciadorFaixa.
const GerenciadorFaixa = ({ faixa, referencia }) => { return <audio src={faixa} ref={referencia} />; }; export default GerenciadorFaixa;
Com isso, conseguiremos tocar e pausar as faixas de áudio do nosso audiobook.
Minicurso de ReactJS – Aula 4 – Controle das Faixas
Vamos para a quarta aula do nosso minicurso de ReactJS e, nessa aula, vou te ensinar como construir o controle das faixas do nosso tocador de audiobook!
Caso prefira esse conteúdo no formato de vídeo-aula, assista ao vídeo abaixo ou acesse o nosso canal do YouTube!
Para receber o(s) arquivo(s) utilizados na aula, preencha seu e-mail:
Na aula de hoje, vou te mostrar como fazer e aplicar as funcionalidades de controle de faixas ao nosso player de audiobook.
Veremos como fazer os ajustes do botão de retroceder e avançar faixas, assim como ajustar o problema de termos faixas além das disponíveis e faixas negativas.
Outro ponto importante que iremos abordar nessa aula é o que acontece ao alterar as faixas: se o tocador já começará tocando a nova faixa ou não.
Faremos esses ajustes todos utilizando o hook useEffect, variáveis de estado e funções! Então, vem comigo para a nossa quarta aula do minicurso de ReactJS.
Botão de Avançar Faixa
Na última aula, construímos e atribuímos as funcionalidades de tocar e pausar a faixa do nosso reprodutor de audiobook.
Nosso próximo passo deve ser o de adicionar inteligência aos botões de retroceder e avançar faixas. Vamos começar pelo botão de avançar.
Para isso, dentro do componente App, no arquivo App.jsx, construiremos a função avancarFaixa. Essa função será responsável por incrementar em 1 o valor da variável de estado faixaAtual através da função definirFaixaAtual.
const avancarFaixa = () => { definirFaixaAtual(faixaAtual + 1) }
Feito isso, vamos disponibilizar essa função de avançar a faixa dentro do componente BotoesControle como uma propriedade.
return ( <> <Capa imagemCapa={informacoesLivro.capa} textoAlternativo={informacoesLivro.textoAlternativo} /> <SeletorCapitulos capituloAtual={faixaAtual + 1} /> <GerenciadorFaixa faixa={informacoesLivro.capitulos[faixaAtual]} referencia={tagAudio} /> <BotoesControle taTocando={taTocando} tocarOuPausarFaixa={tocarOuPausarFaixa} avancarFaixa={avancarFaixa} /> </> );
Dentro do arquivo BotoesControle.jsx, vamos definir a propriedade avancarFaixa dentro do evento onClick do botão de avançar.
const BotoesControle = (props) => { return ( <div className='caixa-botoes'> <button> <i className='bi bi-skip-start'></i> </button> <button> <i className='bi bi-arrow-counterclockwise'></i> </button> <button onClick={props.tocarOuPausarFaixa}> <i className={`bi bi-${ props.taTocando ? 'pause' : 'play' }-circle-fill`}></i> </button> <button> <i className='bi bi-arrow-clockwise'></i> </button> <button onClick={props.avancarFaixa}> <i className='bi bi-skip-end'></i> </button> </div> ); };
Dessa forma, quando o botão de avançar faixa for clicado, o capítulo em execução será alterado.
No entanto, apenas essa abordagem nos traz dois problemas que precisam ser solucionados.
O primeiro é que, se continuarmos clicando sobre o botão de avançar, os números dos capítulos continuarão aumentando, independentemente da quantidade de faixas disponíveis, trazendo também uma inconsistência entre o capítulo exibido e o áudio reproduzido.
Além disso, ao mudar de faixa, precisamos clicar mais de uma vez sobre o botão de Play/Pause para que ele volte a funcionar e reproduzir o audiobook.
Ajustando Avanços Indevidos dos Capítulos
Para evitar os avanços indevidos dos capítulos, ou seja, que eles continuem aumentando enquanto o botão de avançar for pressionado, vamos adicionar uma verificação para garantir se existe uma próxima faixa disponível antes de tentar avançar.
Se não houver mais faixas, o sistema deve retornar para a faixa inicial.
A contagem das faixas, como vimos, começa em 0. Portanto, se existem dois capítulos, o último valor atribuído à faixa que podemos chegar é 1.
Podemos criar essa verificação comparando o número do totalCapitulos com a faixaAtual + 1. Se esses valores forem iguais, significa que chegamos na última faixa disponível e, a partir dela, devemos retornar para a primeira faixa.
Caso contrário, deverá seguir o procedimento que havíamos implementado antes.
const avancarFaixa = () => { if(informacoesLivro.totalCapitulos === faixaAtual + 1) { definirFaixaAtual(0); } else { definirFaixaAtual(faixaAtual + 1) } }
Agora, ao chegarmos no último capítulo e tentarmos avançar, seremos redirecionados para o primeiro capítulo disponível. Dessa forma, conseguimos solucionar nosso primeiro problema.
Botão de Retroceder Faixa
Para o botão de retroceder faixa, vamos fazer um processo bastante semelhante. Mas, nesse caso, iremos verificar se a faixa atual é a faixa 0.
Em caso positivo, vamos definir a faixa atual como sendo a última faixa disponível, ou seja, o total de capítulos menos 1. Assim, ao clicarmos para retroceder, estando no primeiro capítulo, seremos levados diretamente para o último capítulo disponível.
Caso contrário, iremos apenas retroceder uma faixa, subtraindo 1 da faixa atual.
const retrocederFaixa = () => { if (faixaAtual === 0) { definirFaixaAtual(informacoesLivro.totalCapitulos - 1); } else { definirFaixaAtual(faixaAtual - 1) } }
Vamos adicionar essa função às propriedades do nosso BotoesControle.
return ( <> <Capa imagemCapa={informacoesLivro.capa} textoAlternativo={informacoesLivro.textoAlternativo} /> <SeletorCapitulos capituloAtual={faixaAtual + 1} /> <GerenciadorFaixa faixa={informacoesLivro.capitulos[faixaAtual]} referencia={tagAudio} /> <BotoesControle taTocando={taTocando} tocarOuPausarFaixa={tocarOuPausarFaixa} avancarFaixa={avancarFaixa} retrocederFaixa={retrocederFaixa} /> </> ); }
E adicioná-la como um evento onClick do botão de retroceder dentro do arquivo BotoesControle.jsx.
const BotoesControle = (props) => { return ( <div className='caixa-botoes'> <button onClick={props.retrocederFaixa}> <i className='bi bi-skip-start'></i> </button> <button> <i className='bi bi-arrow-counterclockwise'></i> </button> <button onClick={props.tocarOuPausarFaixa}> <i className={`bi bi-${ props.taTocando ? 'pause' : 'play' }-circle-fill`}></i> </button> <button> <i className='bi bi-arrow-clockwise'></i> </button> <button onClick={props.avancarFaixa}> <i className='bi bi-skip-end'></i> </button> </div> ); }; export default BotoesControle;
Dessa forma, já seremos capazes de percorrer totalmente as faixas do nosso livro.
E o mais interessante é que, independentemente de quantos capítulos o livro tenha, esse código funcionará; só precisaríamos alterar no objeto de controle o totalCapitulos para a quantidade de capítulos existentes.
Trocar de Faixas – Botão Play/Pause
O outro problema que precisamos solucionar é referente ao botão de Play e Pause para controlar a reprodução de faixas adequadamente, mesmo quando alterarmos entre capítulos.
Atualmente, quando mudamos de faixa, a interface continua exibindo o botão de Play/Pause no estado anterior dele, enquanto a faixa não acompanha esse estado.
Por exemplo, se o capítulo está sendo reproduzido e mudamos de faixa, o botão de Play fica sendo exibido como Pause, porém o áudio não está mais sendo reproduzido.
Essa dessincronização acontece porque o controle de áudio é gerenciado pelo HTML e não totalmente pelo React, resultando nessa inconsistência entre a interface e o som.
Então, ao trocar a faixa de áudio, a tag de áudio do HTML não comunica automaticamente ao React que uma nova faixa está tocando. Isso resulta em uma dessincronia entre o estado do React e o comportamento da tag de áudio.
Existem duas formas para resolvermos essa questão: permitir que a próxima faixa comece a tocar automaticamente ao trocar a faixa atual ou pausar a faixa atual ao trocar.
A primeira opção é a mais comum em serviços de streaming de áudio como o Spotify e o Audible.
Para fazer isso, vamos usar o hook useEffect, que permite lidar com efeitos colaterais na aplicação.
Ou seja, lidar com algo secundário que acontece como resposta a um evento principal. No nosso contexto, podemos ver isso como a mudança de estado que provoca outra ação dentro do programa.
O primeiro passo será importar esse hook dentro do arquivo App.jsx.
import { useState, useRef, useEffect } from 'react';
Em seguida, podemos declarar esse hook passando para ele duas informações: a função que descreve o efeito colateral desejado e o array que lista as variáveis cujo estado deve ser monitorado.
A função dentro do useEffect será responsável por disparar ações quando as variáveis listadas no array sofrerem alterações. Isso garante que mudanças relevantes acionem os efeitos desejados.
Para o nosso objetivo, vamos colocar a variável faixaAtual dentro do array. Isso significa que, se essa informação for alterada, nosso hook será ativado e executará o efeito colateral.
A lógica desse efeito colateral será a seguinte: se taTocando for verdadeira, ao alterarmos a faixaAtual, iremos chamar a função tocarFaixa.
Ou seja, se o capítulo já estiver sendo reproduzido, ao mudarmos a faixa em execução, o programa irá reconhecer que a faixa está tocando e chamará a função tocarFaixa para começar a reproduzir o próximo capítulo ou o capítulo anterior.
Todo hook precisa ser definido no início do componente. No entanto, a função tocarFaixa está definida posteriormente dentro do código. E o código é lido e executado de cima para baixo.
Dessa forma, ao tentarmos executar nosso programa, teremos um erro pois dentro do hook useEffect tentaremos chamar e executar uma arrow function que ainda não foi definida.
Para solucionar esse problema, podemos transformar a nossa arrow function tocarFaixa em uma função tradicional, uma função padrão.
Isso porque esse tipo de função o JavaScript reconhece e armazena durante a leitura inicial do arquivo, e elas podem ser chamadas a qualquer momento dentro do código.
import { useState, useRef, useEffect } from 'react'; import './App.css'; import 'bootstrap-icons/font/bootstrap-icons.css'; import brasCubasImg from './assets/bras_cubas.jpeg'; import Capa from './Capa'; import SeletorCapitulos from './SeletorCapitulos'; import BotoesControle from './BotoesControle'; import livro from './assets/capitulos/livro'; import GerenciadorFaixa from './GerenciadorFaixa'; function App() { const [taTocando, definirTaTocando] = useState(false); const [faixaAtual, definirFaixaAtual] = useState(0); const tagAudio = useRef(null); useEffect(() => { if(taTocando) { tocarFaixa(); } }, [ faixaAtual ]) const informacoesLivro = { nome: 'Memórias Póstumas de Brás Cubas', autor: 'Machado de Assis', totalCapitulos: 2, capa: brasCubasImg, capitulos: livro, textoAlternativo: 'Capa do livro Memórias Póstumas de Brás Cubas.', }; function tocarFaixa() { tagAudio.current.play(); definirTaTocando(true); };
Essa característica permite uma maior flexibilidade na ordem das definições de funções sem comprometer a execução do programa.
Dessa forma, ajustamos o nosso código e não há mais problemas com os efeitos colaterais e nem com a execução das funções.
Agora, ao trocar as faixas, o sistema respeita se a música deve tocar ou não, conforme definido anteriormente.
Com essas novas funcionalidades implementadas no tocador de audiobooks, conseguimos torná-lo mais completo e inteligente através do uso eficaz dos hooks e funções, principalmente o useEffect.
Minicurso de ReactJS – Aula 5 – Barra de Progresso
Chegamos à quinta aula do nosso minicurso de ReactJS! Nesta aula, vamos desenvolver a barra de progresso do tocador do nosso audiobook.
Caso prefira esse conteúdo no formato de vídeo-aula, assista ao vídeo abaixo ou acesse o nosso canal do YouTube!
Para receber o(s) arquivo(s) utilizados na aula, preencha seu e-mail:
Na aula de hoje eu vou te ensinar a construir a barra de progresso do audiobook e torná-la funcional!
Vamos aprender a criar a barra de progresso e trabalhar com tempo em JavaScript, garantindo que a duração total da faixa seja exibida corretamente para o usuário durante a reprodução do áudio.
Além disso, vamos atualizar a barra de progresso com o tempo atual da faixa, permitindo que você visualize a barra sendo preenchida conforme o audiobook é reproduzido.
Barra de Progresso do Audiobook – ContainerProgresso
O componente ContainerProgresso será responsável por exibir a barra de progresso do reprodutor de audiobook, junto com as métricas de tempo, como o tempo atual e a duração total do capítulo.
Dentro do arquivo ContainerProgresso.jsx, vamos definir o nosso componente que retornará a barra de progresso.
Esse componente será composto por uma tag <section> com a classe container-progresso, que define a área geral da barra, e duas tags <div>, uma com a classe progresso-total e outra com a classe metricas-tempo.
Dentro da <div> com a classe progresso-total, teremos mais duas tags <div>. A primeira, com a classe progresso-atual, indicará visualmente o progresso da faixa, e a segunda, com a classe marcador-posicao, representará a posição atual da faixa.
Já na <div> com a classe metricas-tempo, teremos, por enquanto, dois parágrafos (<p>) para exibir o tempo decorrido da reprodução e o tempo total.
const ContainerProgresso = () => { return ( <section className="container-progresso"> <div className="progresso-total"> <div className="progresso-atual"></div> <div className="marcador-posicao"></div> </div> <div className="metricas-tempo"> <p>00:00</p> <p>02:31</p> </div> </section> ); }; export default ContainerProgresso;
Com o componente criado, vamos importá-lo em nosso aplicativo no arquivo App.jsx.
import { useState, useRef, useEffect } from "react"; import "./App.css"; import "bootstrap-icons/font/bootstrap-icons.css"; import brasCubasImg from "./assets/bras_cubas.jpeg"; import Capa from "./Capa"; import SeletorCapitulos from "./SeletorCapitulos"; import BotoesControle from "./BotoesControle"; import livro from "./assets/capitulos/livro"; import GerenciadorFaixa from "./GerenciadorFaixa"; import ContainerProgresso from "./ContainerProgresso";
E adicioná-lo dentro do return antes do componente BotoesControle para que nossa barra seja renderizada.
return ( <> <Capa imagemCapa={informacoesLivro.capa} textoAlternativo={informacoesLivro.textoAlternativo} /> <SeletorCapitulos capituloAtual={faixaAtual + 1} /> <GerenciadorFaixa faixa={informacoesLivro.capitulos[faixaAtual]} referencia={tagAudio} /> <ContainerProgresso /> <BotoesControle taTocando={taTocando} tocarOuPausarFaixa={tocarOuPausarFaixa} avancarFaixa={avancarFaixa} retrocederFaixa={retrocederFaixa} /> </> ); }
Com isso, nossa barra de progresso será exibida dentro do aplicativo.
No entanto, por enquanto, a barra de progresso é estática, sem nenhuma funcionalidade. É isso que vamos começar a desenvolver agora.
Atualizando a Barra de Progresso – Tempo Total e Tempo Atual da Faixa
Para implementar o tempo total e o tempo atual da faixa, começaremos criando as variáveis de estado dentro do App.jsx.
A variável de estado tempoTotalFaixa será responsável por armazenar a duração total da faixa atual, enquanto tempoAtualFaixa armazenará o tempo atual de reprodução. Ambas iniciarão com o valor de 0.
Cada uma das variáveis de estado terá sua função de atualização correspondente: definirTempoTotalFaixa e definirTempoAtualFaixa.
function App() { const [taTocando, definirTaTocando] = useState(false); const [faixaAtual, definirFaixaAtual] = useState(0); const [tempoTotalFaixa, definirTempoTotalFaixa] = useState(0); const [tempoAtualFaixa, definirTempoAtualFaixa] = useState(0); const tagAudio = useRef(null);
Feito isso, dentro do return do nosso aplicativo, passaremos as funções definirTempoTotalFaixa e definirTempoAtualFaixa como props para o GerenciadorFaixa, e os estados tempoTotalFaixa e tempoAtualFaixa como props para o ContainerProgresso.
return ( <> <Capa imagemCapa={informacoesLivro.capa} textoAlternativo={informacoesLivro.textoAlternativo} /> <SeletorCapitulos capituloAtual={faixaAtual + 1} /> <GerenciadorFaixa faixa={informacoesLivro.capitulos[faixaAtual]} referencia={tagAudio} definirTempoTotalFaixa={definirTempoTotalFaixa} definirTempoAtualFaixa={definirTempoAtualFaixa} /> <ContainerProgresso tempoTotalFaixa={tempoTotalFaixa} tempoAtualFaixa={tempoAtualFaixa} /> <BotoesControle taTocando={taTocando} tocarOuPausarFaixa={tocarOuPausarFaixa} avancarFaixa={avancarFaixa} retrocederFaixa={retrocederFaixa} /> </> ); }
Isso permitirá que o GerenciadorFaixa atualize os estados da faixa conforme a faixa é carregada e reproduzida, e que o ContainerProgresso utilize esses estados para renderizar a barra de progresso e as métricas de tempo corretamente.
Para que o nosso componente GerenciadorFaixa de fato atualize o tempo total e o tempo atual da faixa em reprodução, precisamos implementar essa funcionalidade dentro dele.
Dentro do GerenciadorFaixa.jsx, temos o controle da faixa de áudio contida na tag <audio>. Essa tag HTML contém as informações necessárias para obtermos a duração e o tempo atual que está sendo reproduzido.
Quando o áudio é carregado, são gerados metadados com as informações correspondentes a ele, incluindo a duração total da faixa.
Para pegar essa informação, utilizaremos a propriedade onLoadedMetadata, um evento que é disparado quando os metadados do áudio são carregados e ficam disponíveis.
Dentro desse evento, chamaremos a função definirTempoTotalFaixa para atualizar o estado tempoTotalFaixa com a duração da faixa atual obtida através da propriedade duration.
Já para o tempo atual da faixa, utilizaremos a propriedade onTimeUpdate, um evento que é disparado continuamente enquanto o áudio é reproduzido.
Vamos usar o evento onTimeUpdate para chamar a função definirTempoAtualFaixa e atualizar a variável de estado tempoAtualFaixa com o tempo de reprodução atual obtido através da propriedade currentTime.
Não se esqueça de adicionar as duas funções como props do GerenciadorFaixa.
const GerenciadorFaixa = ({ faixa, referencia, definirTempoTotalFaixa, definirTempoAtualFaixa, }) => { return ( <audio src={faixa} ref={referencia} onLoadedMetadata={() => definirTempoTotalFaixa(referencia.current.duration) } onTimeUpdate={() => definirTempoAtualFaixa(referencia.current.currentTime) } /> ); }; export default GerenciadorFaixa;
Agora que conseguimos obter e atualizar as informações sobre tempoTotalFaixa e tempoAtualFaixa, vamos exibir essas informações dentro do componente ContainerProgresso.jsx.
Esse componente receberá as variáveis de estado tempoTotalFaixa e tempoAtualFaixa como props e as utilizará para renderizar a barra de progresso e as métricas de tempo.
Antes de renderizarmos o conteúdo do componente, definiremos uma função auxiliar chamada formatarTempo para converter o valor obtido em um formato de minutos e segundos (MM:SS).
Essa função JavaScript cria um objeto Date (data e hora) com o valor nulo e define os segundos usando o método setSeconds. Em seguida, converte o valor para uma string no formato ISO usando o método toISOString.
Por fim, utilizamos o método slice para extrair apenas a parte correspondente aos minutos e segundos, descartando a parte da data e da hora.
Com a função formatarTempo definida, podemos atualizar a nossa barra de progresso para que ela se torne dinâmica e exiba os valores de tempo corretamente.
Começando pela <div> progresso-atual, vamos adicionar o estilo width (largura) do CSS dentro da tag para definir dinamicamente a porcentagem de largura que a barra deve ocupar.
A porcentagem de largura será calculada pela fórmula: (tempoAtualFaixa * 100) / tempoTotalFaixa.
Isso garante que a largura da barra de progresso aumente proporcionalmente ao tempo atual de reprodução.
Para a <div> com a classe marcador-posicao, definiremos o estilo left, calculado da mesma forma que a largura da progresso-atual. Esse estilo define a posição do marcador ao longo da barra a partir da esquerda.
Por fim, para as métricas de tempo, substituiremos os valores estáticos pelas variáveis de estado, utilizando a função formatarTempo para exibir os tempos em minutos e segundos.
const ContainerProgresso = ({ tempoTotalFaixa, tempoAtualFaixa }) => { const formatarTempo = (tempoEmSegundos) => { const tempo = new Date(null); tempo.setSeconds(tempoEmSegundos); return tempo.toISOString().slice(14, 19); }; return ( <section className="container-progresso"> <div className="progresso-total"> <div className="progresso-atual" style={{ width: `${(tempoAtualFaixa * 100) / tempoTotalFaixa}%`, }} ></div> <div className="marcador-posicao" style={{ left: `${(tempoAtualFaixa * 100) / tempoTotalFaixa}%`, }} ></div> </div> <div className="metricas-tempo"> <p>{formatarTempo(tempoAtualFaixa)}</p> <p>{formatarTempo(tempoTotalFaixa)}</p> </div> </section> ); }; export default ContainerProgresso;
Com isso, nossa barra de progresso estará funcional, atualizando-se conforme a reprodução da faixa de áudio no nosso tocador de audiobook.
Nosso tocador de audiobook está quase completo. Na próxima aula, implementaremos os botões para avançar e retroceder a faixa.
Minicurso de ReactJS – Aula 6 – Finalizando o Tocador de Audiobook
Vamos para a sexta aula do nosso minicurso de ReactJS! Nesta aula, iremos finalizar o tocador de audiobook adicionando as funcionalidades de avançar e retroceder segundos da faixa.
Caso prefira esse conteúdo no formato de vídeo-aula, assista ao vídeo abaixo ou acesse o nosso canal do YouTube!
Para receber o(s) arquivo(s) utilizados na aula, preencha seu e-mail:
Chegamos à nossa sexta aula do minicurso de ReactJS, e nela você aprenderá como adicionar as funcionalidades de avançar e retroceder segundos dentro de uma faixa de áudio.
Além disso, vou te mostrar como clicar sobre a barra de progresso para posicionar o reprodutor de áudio em uma parte específica do audiobook, permitindo ao usuário avançar a faixa para a parte desejada.
Essa é a última etapa que precisamos concluir para ter o nosso tocador de audiobook completo e totalmente funcional!
Avançar e Retroceder o Tempo da Faixa
Na aula passada, construímos a barra de progresso com o tempo atual e total da faixa reproduzida. Fizemos isso através das informações contidas dentro da tag <audio>.
Nesta aula, continuaremos trabalhando com a tag <audio> para criar e configurar os botões de avançar e retroceder o tempo da faixa.
Por padrão, utilizaremos 15 segundos tanto para avançar quanto para retroceder. Você pode modificar esse tempo de acordo com suas necessidades.
Esse processo será bastante simples. Para começar, dentro do componente App, no arquivo App.jsx, construiremos as duas funções necessárias para avançar e retroceder: avancar15s e retroceder15s.
Essas funções acessarão a tag de áudio através da referência tagDeAudio.current para interagir com a faixa de áudio atual e modificar a propriedade currentTime, que representa o tempo atual de reprodução da faixa.
Para avancar15s, iremos somar 15 segundos a currentTime, enquanto para retroceder15s iremos subtrair esse mesmo tempo.
const avancar15s = () => { tagAudio.current.currentTime += 15; }; const retroceder15s = () => { tagAudio.current.currentTime -= 15; };
Com as funções criadas, podemos passá-las como props para o componente BotoesControle.
<BotoesControle taTocando={taTocando} tocarOuPausarFaixa={tocarOuPausarFaixa} avancarFaixa={avancarFaixa} retrocederFaixa={retrocederFaixa} avancar15s={avancar15s} retroceder15s={retroceder15s} />
Feito isso, dentro do componente BotoesControle.jsx, adicionaremos as referências a essas propriedades e implementaremos as funcionalidades delas aos botões correspondentes.
const BotoesControle = ({ retrocederFaixa, tocarOuPausarFaixa, taTocando, avancarFaixa, avancar15s, retroceder15s, }) => { return ( <div className="caixa-botoes"> <button onClick={retrocederFaixa}> <i className="bi bi-skip-start"></i> </button> <button onClick={retroceder15s}> <i className="bi bi-arrow-counterclockwise"></i> </button> <button onClick={tocarOuPausarFaixa}> <i className={`bi bi-${taTocando ? "pause" : "play"}-circle-fill`}></i> </button> <button onClick={avancar15s}> <i className="bi bi-arrow-clockwise"></i> </button> <button onClick={avancarFaixa}> <i className="bi bi-skip-end"></i> </button> </div> ); }; export default BotoesControle;
Note que, para melhorar a legibilidade do código, desestruturei as props nas funcionalidades específicas que estão sendo importadas para o nosso componente.
Isso evita a necessidade de escrever props.nomeDaProp repetidamente, tornando nosso código mais claro e organizado.
Uma característica muito interessante do React que podemos notar após implementar essas funcionalidades é a sincronização entre o estado atual do componente e a interface.
O tempo atual da faixa (currentTime) é usado para calcular a barra de progresso e outros elementos visuais do nosso tocador.
Enquanto o atributo onTimeUpdate da tag de áudio garante que a variável de estado que controla o tempo atual seja atualizada conforme a reprodução avança, isso mantém a interface sincronizada com o estado real do áudio.
E graças às variáveis de estado que configuramos e construímos nas aulas passadas para controlar e gerenciar o tempo da nossa faixa, conseguimos implementar as funções de avançar e retroceder 15 segundos de forma tão prática.
Clique na Barra de Progresso
Para construirmos a funcionalidade de clicar na barra de progresso para selecionar qual parte da faixa desejamos reproduzir, primeiro precisamos criar uma referência à barra de progresso usando o hook useRef dentro do nosso aplicativo App.jsx.
function App() { const [taTocando, definirTaTocando] = useState(false); const [faixaAtual, definirFaixaAtual] = useState(0); const [tempoTotalFaixa, definirTempoTotalFaixa] = useState(0); const [tempoAtualFaixa, definirTempoAtualFaixa] = useState(0); const tagAudio = useRef(null); const barraProgresso = useRef(null);
Em seguida, vamos passar essa referência para o componente ContainerProgresso que tem o controle da nossa barra.
<ContainerProgresso tempoTotalFaixa={tempoTotalFaixa} tempoAtualFaixa={tempoAtualFaixa} referencia={barraProgresso} />
Dentro do arquivo do componente, ContainerProgresso.jsx, vamos passar essa referência como uma propriedade e utilizá-la dentro da nossa <div> progresso-total.
Essa div é responsável por armazenar as informações da barra de progresso inteira. Sendo assim, podemos usar essa referência para determinar em qual parte da barra de progresso o usuário clicou.
const ContainerProgresso = ({ tempoTotalFaixa, tempoAtualFaixa, referencia, }) => { const formatarTempo = (tempoEmSegundos) => { const tempo = new Date(null); tempo.setSeconds(tempoEmSegundos); return tempo.toISOString().slice(14, 19); }; return ( <section className="container-progresso"> <div className="progresso-total" ref={referencia}>
Dessa forma, a nossa variável barraProgresso fará referência à <div> correspondente à barra de progresso total.
Feito isso, vamos retornar ao arquivo App.jsx e criar a função avancarPara que permitirá ao usuário clicar na barra de progresso e saltar para um ponto específico da faixa de áudio.
Essa função recebe como parâmetro um objeto evento, que é gerado pelo clique do usuário na barra de progresso. Esse evento traz informações detalhadas como a posição do clique feito.
Além disso, vamos acessar a largura total da barra de progresso através da referência criada à tag <div> que a representa (barraProgresso).
Dentro dessa função, calcularemos o novoTempo da faixa, pegando a posição horizontal do clique dentro da barra de progresso (evento.nativeEvent.offsetX) e dividindo esse valor pelo comprimento da barra onde o clique ocorreu.
Dessa forma, obtemos a proporção do comprimento da barra onde o clique foi feito, um valor entre 0 e 1.
Por fim, multiplicamos esse valor pelo tempoTotalFaixa (duração total da faixa de áudio atual), obtendo assim o tempo correspondente na faixa para o ponto clicado.
Com esse valor, podemos atualizar a tagAudio, atribuindo a ela o tempo correspondente ao ponto clicado na barra de progresso, fazendo com que o áudio avance ou retroceda até esse momento.
const avancarPara = (evento) => { const largura = barraProgresso.current.clientWidth; const novoTempo = (evento.nativeEvent.offsetX / largura) * tempoTotalFaixa; tagAudio.current.currentTime = novoTempo; };
Com a função definida, vamos disponibilizá-la dentro do componente ContainerProgresso.
<ContainerProgresso tempoTotalFaixa={tempoTotalFaixa} tempoAtualFaixa={tempoAtualFaixa} referencia={barraProgresso} avancarPara={avancarPara} />
E, para finalizar, dentro do arquivo ContainerProgresso.jsx, definiremos essa função dentro do evento onClick na barra de progresso.
const ContainerProgresso = ({ tempoTotalFaixa, tempoAtualFaixa, referencia, avancarPara, }) => { const formatarTempo = (tempoEmSegundos) => { const tempo = new Date(null); tempo.setSeconds(tempoEmSegundos); return tempo.toISOString().slice(14, 19); }; return ( <section className="container-progresso" onClick={avancarPara}> <div className="progresso-total" ref={referencia} onClick={avancarPara}> <div className="progresso-atual" style={{ width: `${(tempoAtualFaixa * 100) / tempoTotalFaixa}%`, }} ></div> <div className="marcador-posicao" style={{ left: `${(tempoAtualFaixa * 100) / tempoTotalFaixa}%`, }} ></div> </div> <div className="metricas-tempo"> <p>{formatarTempo(tempoAtualFaixa)}</p> <p>{formatarTempo(tempoTotalFaixa)}</p> </div> </section> ); }; export default ContainerProgresso;
Nesta aula, concluímos o tocador de audiobook com React, implementando funcionalidades essenciais como avançar e retroceder a faixa em 15 segundos e a capacidade de saltar para uma posição específica clicando na barra de progresso.
Com essas adições, nosso tocador está completo e pronto para uso, proporcionando uma experiência de usuário eficiente e intuitiva.
Minicurso de ReactJS – Aula Bônus – Como Estilizar a sua Aplicação
Chegamos à sétima e última aula do Minicurso de ReactJS! Nesta aula bônus, vou te mostrar como realizar a estilização em CSS para a página do nosso tocador de audiobook.
Caso prefira esse conteúdo no formato de vídeo-aula, assista ao vídeo abaixo ou acesse o nosso canal do YouTube!
Nas seis primeiras aulas, construímos o tocador de audiobook, mas a parte visual já estava pronta no arquivo style.css, que disponibilizei para download.
Agora, vou apagar esse arquivo e explicar as principais estilizações que utilizamos para que você entenda como usar o CSS para personalizar e estilizar seus futuros projetos.
A estilização é uma parte essencial no desenvolvimento front-end de qualquer aplicação web. No React, o CSS desempenha um papel fundamental na criação de interfaces agradáveis e funcionais.
Nesta aula, vou te mostrar técnicas e práticas recomendadas para estilizar esta e outras aplicações em React!
Introdução ao CSS
O CSS, ou Cascading Style Sheets, é a linguagem usada para descrever a aparência de um documento HTML.
No contexto de uma aplicação em ReactJS, como nosso tocador de audiobook, o CSS pode ser utilizado de várias formas, desde a aplicação de estilos globais até a customização de componentes específicos.
A integração eficiente do CSS permite que a aplicação tenha uma interface atraente e intuitiva para o usuário, sendo não apenas visualmente agradável, mas também acessível e responsiva.
Leia também: Curso de Introdução ao HTML e CSS – Criando a Página do Google
Preparação dos Arquivos CSS
Para gerar nosso projeto em ReactJS, utilizamos o Vite, que cria a estrutura inicial do projeto. Essa estrutura inclui dois arquivos CSS: index.css e App.css.
Vou remover o conteúdo desses dois arquivos para que possamos construir a estilização do zero, concentrando todo o processo no arquivo App.css.
Com a remoção do conteúdo, nossa aplicação ficará com uma interface básica e funcional, porém nada atraente ou intuitiva:
Definições Globais de Estilo
Vamos iniciar a estilização do nosso tocador de audiobook! No arquivo App.css, criaremos o seletor :root.
Este seletor se refere ao elemento raiz do documento. Os estilos definidos dentro dele serão aplicados globalmente, permitindo que você estabeleça valores padrão para todos os elementos.
Dentro do :root, podemos definir variáveis CSS globais. Essas variáveis nos permitem reutilizar valores em diferentes partes do código, garantindo consistência e facilitando a manutenção e a personalização de estilos padrões.
Começaremos definindo as propriedades relacionadas à fonte que será utilizada por padrão na aplicação: font-family (família da fonte), font-weight (peso da fonte) e font-size (tamanho da fonte).
A font-family será definida como sans-serif. Você pode passar uma lista de fontes para a sua aplicação, mas neste caso utilizaremos apenas essa, que é uma fonte sem aqueles pequenos traços decorativos nas extremidades das letras.
O font-weight será de 400, que corresponde ao estilo normal, sem ser muito fino ou grosso. O font-size será 1.3rem, onde “rem” é uma unidade relativa ao tamanho padrão do root, que é 16px. Assim, o tamanho base da fonte será 1.3 * 16px.
Com a fonte definida, vamos passar para o plano de fundo da aplicação. Nosso tocador de audiobook possui um fundo com gradiente de cor que começa em um tom de amarelo, próximo ao cinza claro, e escurece até um azul quase preto.
Para criar esse efeito com CSS, usaremos a propriedade background-image com a função linear-gradient.
Essa função cria gradientes lineares, ou seja, transições suaves entre duas ou mais cores ao longo de uma linha reta. As cores são definidas por seus códigos hexadecimais na ordem em que devem se alterar.
Por fim, definiremos a propriedade box-sizing como border-box. Isso faz com que os valores de padding (espaçamento interno) e border (borda) dos elementos sejam incluídos no valor total do elemento.
Dessa forma, todos os elementos da página/aplicação terão seu tamanho incluindo o conteúdo, o espaçamento interno e a borda no valor definido para que eles ocupem.
Esse é um procedimento comum no CSS, pois garante que os elementos ocupem o espaço desejado de forma mais previsível.
Caso você queira entender melhor o os conceitos sobre o border-box, vou deixar uma aula sobre Box Model, onde explico detalhadamente esse conceito: Box Model – O conceito mais importante do CSS
Nosso seletor ficará assim:
:root { font-family: sans-serif font-weight: 400; font-size: 1.3rem; background-image: linear-gradient( #dcddc1, #3d443a, #0a1b2a, #010d19, ); box-sizing: border-box; }
Além dessas propriedades, precisamos definir a cor do texto em toda a aplicação utilizando a propriedade color. No entanto, antes disso, abordaremos um conceito importante do CSS: as variáveis.
Em CSS, podemos criar variáveis utilizando o prefixo — seguido pelo nome da variável. Dentro delas, podemos determinar cores, tamanhos de fonte e outros valores que podem ser reutilizados ao longo do código.
Para usar uma variável CSS, basta chamar a função var(), passando o nome da variável como argumento.
Criaremos duas variáveis dentro do seletor :root: –light-gray e –font-color. A variável –light-gray armazenará um tom de cinza claro que utilizaremos em várias partes da aplicação.
Já a –font-color será a cor padrão do texto, definida no modelo RGBA, que permite ajustar a opacidade da cor.
O uso do RGBA nos permite definir a quantidade de vermelho (R), verde (G) e azul (B) que uma cor deve ter, essa quantidade varia de 0 a 255. Além disso, temos o A representando o nível de opacidade e variando de 0 a 1.
O RGBA oferece mais versatilidade ao design, pois nos permite controlar a transparência das cores. No caso da –font-color, utilizaremos a cor branca com 87% de opacidade: rgba(255, 255, 255, 0.87).
Para definir a cor do texto, utilizaremos a função var() passando a variável –font-color.
:root { font-family: sans-serif font-weight: 400; font-size: 1.3rem; background-image: linear-gradient( #dcddc1, #3d443a, #0a1b2a, #010d19, ); box-sizing: border-box; --light-gray: #606f81; --font-color: rgba(255, 255, 255, 0.87); color:var(--font-color); }
Dessa forma, temos a base visual da nossa aplicação definida.
Seletor de ID no CSS
No React, a aplicação é geralmente operada dentro de um elemento HTML específico, frequentemente uma <div> que possui o ID root. Esta <div> é o ponto de entrada da aplicação, onde todos os componentes React são renderizados.
Ao estilizar esse elemento, você aplica estilos diretamente ao container principal da aplicação. No CSS, quando queremos selecionar um elemento pelo seu ID, utilizamos a notação #nome-do-id. Portanto, para o nosso caso, definiremos o seletor #root.
Dentro deste seletor, configuraremos corretamente as dimensões e o posicionamento da aplicação, para que ela tenha uma interface responsiva, adaptada a diferentes tamanhos de tela.
Vamos começar definindo a propriedade width (largura) para ocupar 100% do espaço disponível, enquanto a height (altura) será ajustada para 100% da altura disponível na tela, usando a unidade dvh, que considera a altura dinâmica disponível da janela.
Para centralizar o container horizontalmente, aplicaremos a margem (margin) como 0 auto. Dessa forma, a margem superior e inferior serão de 0px, enquanto as margens esquerda e direita serão calculadas automaticamente para centralizar a aplicação.
Além disso, alinharemos o conteúdo do texto dentro do container ao centro horizontalmente, utilizando a propriedade text-align: center.
#root { width: 100%; height: 100dvh; margin: 0 auto; text-align: center; }
Em seguida, transformaremos esse elemento em um flex container, organizando os elementos filhos contidos nele em uma coluna, com a propriedade flex-direction: column.
Esses elementos serão alinhados ao início da coluna (justify-content: start) e centralizados horizontalmente (align-items: center).
#root { width: 100%; height: 100dvh; margin: 0 auto; text-align: center; display: flex; flex-direction: column; justify-content: start; align-items: center; }
Leia também: Flexbox CSS – Noções essenciais para um Layout Flexível
Estilização dos Botões
Para exibir o ícone de uma mãozinha ao posicionar o cursor do mouse sobre os botões do nosso tocador de audiobook, vamos definir o seletor de elemento button com a propriedade cursor e o valor pointer.
button { cursor: pointer; }
Quando desejamos aplicar estilos a todos os elementos de um tipo específico, como neste caso aos botões, usamos seletores de elementos. Isso permite estilizar todos os elementos <button> da aplicação de forma consistente.
Estilização da Capa
Para estilizar a capa, utilizaremos o seletor de classe no CSS. Esse seletor é composto pelo prefixo ponto (.) seguido do nome da classe a ser estilizada. Nesse caso, criaremos o seletor .capa para estilizar todos os elementos que possuem a classe capa.
Ajustaremos a margem superior (margin-top) para 20% da altura da viewport (janela), garantindo um espaçamento adequado acima da capa.
A propriedade object-fit, com o valor contain, será utilizada para ajustar a imagem dentro do elemento, mantendo a proporção original sem cortar o conteúdo, garantindo que ele fique inteiramente visível.
Por fim, definiremos a altura da capa como sendo 30% da altura da viewport (height: 30dvh), ajustando seu tamanho de acordo com a tela do dispositivo.
.capa { margin-top: 20dvh; object-fit: contain; height: 30dvh; }
Estilização do Seletor de Capítulos
A estilização do seletor de capítulos será feita utilizando o seletor de classe .seletor.
Inicialmente, ajustaremos a margem superior para 12% da altura da janela (margin-top: 12dvh), criando um espaço entre a capa e o seletor de capítulos.
A largura será definida para ocupar 100% do espaço disponível (width: 100%), e a cor de fundo será transparente (background-color: transparent).
Também transformaremos nosso seletor em um flex container com a propriedade display: flex, permitindo que os elementos filhos (ícone e texto) sejam organizados de forma flexível.
Esses elementos serão centralizados horizontalmente (justify-content: center) e verticalmente (align-items: center).
Iremos remover a borda, definindo a propriedade border como none, enquanto a cor do texto será definida pela variável –font-color, criada anteriormente nas variáveis globais.
.seletor { margin-top: 12dvh; width: 100%; background-color: transparent; display: flex; justify-content: center; align-items: center; border: none; color: var(--font-color); }
Agora, uma atenção especial para a notação & > p, que utilizaremos. Esse é um seletor aninhado onde o & representa o seletor pai (.seletor), e o > p seleciona especificamente os elementos <p> que são filhos diretos do elemento com a classe seletor.
Em outras palavras, essa regra estiliza apenas os parágrafos <p> que estão diretamente dentro do elemento com a classe seletor.
Nesse seletor aninhado, definiremos um espaçamento interno à esquerda de 0.6rem (padding-left: 0.6rem). Além disso, vamos aumentar o peso da fonte para 800 (font-weight), dando destaque a ela, e ajustaremos o tamanho da fonte (font-size) para 1rem.
Com isso, nosso seletor de capítulos terá a formatação desejada, aproximando toda a aplicação do resultado esperado, com apenas alguns ajustes restantes.
Estilizando o Container de Progresso
O container de progresso agrupa as barras de progresso do tocador de audiobook, bem como os marcadores e métricas de tempo. Vamos começar a estilizar toda essa seção por ele.
Definiremos o seletor de classe .container-progresso com a largura ajustada para ocupar 100% do espaço disponível (width: 100%).
Este container será transformado em um flex container (display: flex), o que facilitará a organização dos elementos filhos.
A direção dos elementos filhos será definida como coluna (flex-direction: column), significando que eles serão empilhados verticalmente.
Para garantir que sejam centralizados horizontalmente, utilizamos a propriedade align-items: center.
Por fim, aplicamos um padding superior de 1rem (padding-top: 1rem), criando um pequeno espaçamento na parte superior do container.
.container-progresso { width: 100%; display: flex; flex-direction: column; align-items: center; padding-top: 1rem; }
Estilizando as Barras de Progresso – Progresso Total e Progresso Atual
Para estilizar as barras de progresso, usaremos os seletores .progresso-total e .progresso-atual.
Começando pela barra de progresso total, configuraremos a largura mínima (min-width) para 60% do espaço disponível, enquanto a largura máxima será limitada a 400px (max-width: 400px).
Isso garantirá a responsividade da aplicação, adaptando-se a diferentes tamanhos de tela.
A altura da barra de progresso total será definida como 5px, e a cor de fundo será aplicada através da variável –light-gray, criada anteriormente.
Para garantir o espaçamento interno, aplicaremos um padding de 3 rems nas laterais, e, através da propriedade border-radius, arredondaremos os cantos da barra, criando bordas mais suaves.
Para que essa barra sirva como referência para o posicionamento absoluto dos elementos filhos, definiremos a propriedade position como relative.
.progresso-total { min-width: 60%; max-width: 400px; height: 5px; background-color: var(--light-gray); padding: 0 3rem; border-radius: 10px; position: relative; }
A barra de progresso atual representa a parte preenchida da barra de progresso.
Para que esse preenchimento siga o layout da barra de progresso total, iremos posicioná-la de forma absoluta (position: absolute) e ancorar sua posição no topo (top: 0) e à esquerda (left: 0) do elemento pai .progresso-total.
Isso significa que a barra de progresso começa exatamente no início da barra total e cresce proporcionalmente conforme avança.
A altura será mantida em 5px para corresponder à altura da barra total, e a cor de fundo será um tom de laranja vibrante, que contrasta com o cinza claro da barra de fundo.
Para finalizar, arredondaremos as bordas com a propriedade border-radius.
.progresso-atual { position: absolute; top: 0; left: 0; height: 5px; background-color: #feb332; border-radius: 10px; }
Estilizando o Marcador de Posição
Com as barras de progresso total e atual concluídas, partimos para a estilização do marcador de posição, a bolinha que indica a posição específica na barra de progresso.
Para posicioná-lo corretamente, utilizamos a propriedade position: absolute, garantindo que ele seja colocado precisamente em relação ao elemento pai.
A altura e a largura do marcador serão ambas definidas como 25px, e a cor de fundo será o mesmo tom de laranja vibrante utilizado na barra de progresso atual.
Arredondaremos completamente as bordas com a propriedade border-radius, transformando-o em um círculo perfeito.
.marcador-posicao { position: absolute; height: 25px; width: 25px; background-color: #feb332; top: -170%; border-radius: 50%; }
Estilizando as Métricas de Tempo
Dentro do nosso tocador de audiobook, temos as métricas que indicam o tempo decorrido e o tempo total de cada capítulo do livro. Vamos estilizar esses elementos utilizando o seletor .metricas-tempo.
Essas métricas ocuparão 70% da largura disponível (width: 70%) e serão um flex container (display: flex), mas, nesse caso, os elementos filhos serão organizados em uma linha.
Como o valor padrão para a propriedade flex-direction já é row, não precisamos declará-la explicitamente.
Além disso, a propriedade justify-content será definida como space-between para distribuir os elementos uniformemente, maximizando o espaço entre eles e posicionando cada métrica em uma extremidade da barra de progresso.
A cor do texto será definida pela variável –light-gray, e o peso da fonte será ajustado para 600, destacando levemente as métricas. A margem superior será de 0.1rem, garantindo um pequeno espaçamento em relação ao elemento acima.
.metricas-tempo { width: 70%; display: flex; justify-content: space-between; color: var(--light-gray); font-weight: 600; margin-top: 0.1rem; }
Estilizando os Botões
Agora, com o estilo do tocador de audiobook quase finalizado, falta apenas estilizar os botões. Para isso, vamos criar o seletor .caixa-botoes.
Para organizar e distribuir os botões, definiremos a caixa de botões como um flex container (display: flex) e utilizaremos a propriedade justify-content com o valor space-between.
Isso permitirá que os botões sejam distribuídos uniformemente ao longo do espaço disponível.
Além disso, centralizaremos os botões verticalmente com a propriedade align-items: center e definiremos a largura da caixa para 60% (width: 60%), o que manterá os botões mais centralizados na tela.
.caixa-botoes { display: flex; justify-content: space-between; align-items: center; width: 60%; }
Usaremos novamente a notação & >, desta vez para selecionar especificamente os botões dentro da nossa caixa de botões.
Definiremos a cor de fundo dos botões como transparente (background-color: transparent) e removeremos as bordas (border: none).
O tamanho da fonte dos botões será ajustado para 1.5rem, proporcionando uma boa visibilidade, e a cor do texto será definida pela variável –font-color.
.caixa-botoes { display: flex; justify-content: space-between; align-items: center; width: 60%; & > button { background-color: transparent; border: none; font-size: 1.5rem; color: var(--font-color); }
Além disso, utilizaremos uma notação especial & :not(:first-child):not(:last-child), que cria um seletor aplicando estilos a todos os elementos dentro de .caixa-botoes que não sejam o primeiro nem o último elemento filho.
Assim, conseguimos aplicar estilos diferenciados apenas para os botões intermediários, de avançar e retroceder tempo na faixa, e também para o botão Play/Pause.
Nesse seletor, definiremos o tamanho da fonte como 2rem, destacando esses botões em relação aos botões de mudança de faixa.
.caixa-botoes { display: flex; justify-content: space-between; align-items: center; width: 60%; & > button { background-color: transparent; border: none; font-size: 1.5rem; color: var(--font-color); } & :not(:first-child):not(:last-child) { font-size: 2rem; } }
Dessa forma, concluímos a estilização do nosso tocador de audiobook. As técnicas e propriedades abordadas aqui são fundamentais para qualquer projeto que você venha a construir.
Estilizar uma aplicação React com CSS é um processo essencial para criar uma interface interativa e atraente. Aplicando os seletores e propriedades adequadamente, você garante que sua aplicação não apenas funcione bem, mas também ofereça uma excelente experiência visual aos usuários.
Conclusão – Minicurso de ReactJS
Ao longo desse Minicurso de ReactJS você aprendeu sobre o que é o ReactJS, como utilizá-lo e quais os principais conceitos e funcionalidades oferecidos por essa ferramenta tão importante.
Com isso, além de você reforçar e melhorar seu conhecimento sobre HTML, CSS e JavaScript, você também aprendeu como usar o React para criação de um projeto completo, uma réplica do Audible da Amazon, que é um reprodutor de audiobook!
Hashtag Treinamentos
Para acessar publicações de JavaScript, clique aqui!
Posts mais recentes de JavaScript
- Vue.js: o que é, como funciona, vantagens e como usarDescubra o que é Vue.js, suas vantagens e como começar a usar esse framework moderno para criar projetos incríveis. Aprenda agora!
- O que é arquivo JSON e como usá-lo na prática? [Guia completo]Descubra o que é um arquivo JSON, sua estrutura, vantagens e como usá-lo na prática. Aprenda a criar, abrir e aplicar JSON em seus projetos!
- Como criar gráficos com Chart.js e React – Passo a passoVamos aprender passo a passo como criar gráficos com Chart.js e React, desde a configuração até a integração com uma API de vendas.
Posts mais recentes da Hashtag Treinamentos
- 5 Boas Práticas de SQL para Iniciantes: Otimize Suas Consultas e Organize Seu CódigoSe você está começando com MySQL ou busca melhorar suas habilidades em SQL, este post traz cinco boas práticas de escrita de SQL para iniciantes.
- Função SE Excel: Exemplos e Dicas para Uso [Guia]A função SE Excel é muito útil para definir condições e criar uma planilha mais completa e funcional. Confira nesta aula!
- TCP e UDP: o que são, diferenças e quando usarEntenda o que é TCP e UDP, as diferenças entre os protocolos, suas vantagens e quando usar cada um. Aprenda com exemplos práticos e linguagem acessível.
Expert em conteúdos da Hashtag Treinamentos. Auxilia na criação de conteúdos de variados temas voltados para aqueles que acompanham nossos canais.