Uma ferramenta para marcar suas fotos! Nesta atividade vamos praticar:
- diferentes eventos (
mouseover
,mouseout
,mousemove
,input
); - colocar e remover classes;
- definir o conteúdo de um elemento;
- exercitar template strings;
- recuperar e definir atributos de dados;
- interagir com vários tipos de campos de dados e de escolha;
- uso da propriedade CSS
filter
; - uso da
FileReader
API trocar a foto (desafio opcional).
Crie uma página interativa que permite o usuário configurar 2 marcações em uma foto e que mostram um balãozinho com algumas informações quando se passa o mouse sobre elas.
Além da funcionalidade de visualizar as informações das marcações no balão, também deve ser possível definir diferentes propriedades das marcações (repare a seção de controles à direita da imagem), como sua posição, tamanho, formato, título, conteúdo e cor do texto no balãozinho.
Nesta atividade, serão sugeridas formas de implementação, mas fique à vontade implementar a mesma funcionalidade de outra forma.
A estrutura da página...
A estrutura HTML do trecho relevante da página para a atividade é o seguinte:
body
//...
main
//...
div.linha
div.foto-anotada
img
div.marcacao
div.marcacao
div#balaozinho
section.controles
//...
label
input
label
input
//...
Nessa estrutura, há uma <div class="foto-anotada">
que possui a foto e duas <div class="marcacao">
representando as regiões onde podemos passar o mouse em cima para obter mais informações. Essas são apresentadas dentro do <div id="balaozinho">
. E os controles dentro de <section class="controles">
permitem configurar as informações dessas marcações, dentre outras coisas.
Para esquentar: no arquivo script/controles-marcacao.js
faça com que, ao marcar ou desmarcar o campo ocultar marcações
, as duas .marcacao
desapareçam (e não respondam a eventos de mouse).
Sugestão de implementação...
Use a classe marcacoes-ocultas
em um ancestral das marcações (eg, o body
) e crie uma regra CSS que as deixa invisíveis (selecione as .marcacoes
que tenham como ancestral alguém com a classe marcacoes-ocultas
).
Se reparar o código HTML, verá que o <input type="checkbox">
inclusive possui como value
o nome dessa classe que você pode criar:
<label>ocultar marcações:
<input type="checkbox" id="visibilidade-das-marcacoes" value="marcacoes-ocultas">
</label>
Veja o FAQ para se lembrar de como adicionar ou remover classes de um elemento HTML. Também há informação sobre como alterar a visibilidade de elementos e como saber se um checkbox está marcado.
Nessa linha, uma estratégia é adicionar um evento de input
ou change
no checkbox e, em sua callback, inserir ou retirar a classe que é value
do checkbox (pra não ter que digitar de novo, basta pegar seu value
). Para saber se deve colocar ou retirar, pode-se verificar se o checkbox está marcado ou não, e chamar a el.classList.toggle()
de acordo.
No arquivo script/balaozinho.js
, este exercício pede que você configure o #balaozinho
para que ele:
- Apareça quando o mouse está sobre uma
.marcacao
- Ele deve ter o seguinte conteúdo:
<div id="balaozinho"> <h2>...título...</h2> <p>...conteúdo...</p> </div>
- O título e conteúdo devem ser preenchidos como os atributos
data-titulo
edata-conteudo
da.marcacao
.- OBSERVAÇÃO: é uma boa oportunidade para usar template strings ao definir o conteúdo do balão (veja slides sobre template strings)
- A cor do texto do balãozinho deve ser definida como o valor do atributo
data-cor
da.marcacao
.- Veja no FAQ como alterar o estilo de um elemento HTML via JavaScript
- Ele deve ter o seguinte conteúdo:
- Desapareça quando o mouse sair da
.marcacao
- Se movimente com sua posição sendo a mesma do mouse enquanto ele estiver em cima da
.marcacao
Lembre-se dos eventos de mouse no FAQ.
Sugestão de implementação
- No arquivo
script/balaozinho.js
, crie código para:- Recuperar o
#balaozinho
- Recuperar todas as
.marcacao
- Para cada uma:
- Registrar função para evento de "mouse entrou"
- Determinar qual das marcações foi alvo do evento
- Definir seu conteúdo (FAQ: como definir o conteúdo de um elemento) como os atributos de dados referentes ao título e conteúdo da marcação alvo do evento
- Definir a
color
do balãozinho com o valor dodata-cor
da marcação
- Registrar função para evento de "mouse saiu"
- Simplesmente remover todos os filhos do
#balaozinho
- No FAQ tem como limpar um elemento
- Simplesmente remover todos os filhos do
- Registrar função para evento de "mouse movimentou"
- Definir as propriedades
left
,top
do#balaozinho
de acordo com a posição do mouse- O FAQ mostra como pegar a posição do mouse
- Não se esqueça que
e.pageX
ee.pageY
são números sem unidade de medida e que você deve concatená-los compx
quando for definir os valores deleft
etop
- Definir as propriedades
- Registrar função para evento de "mouse entrou"
- Recuperar o
Quais estilos do balãozinho...
O #balaozinho
é uma <div>
posicionada de forma absoluta (para podermos definir exatos (x,y)), e outras propriedades para deixá-lo bonitinho:
#balaozinho {
position: absolute;
border: 1px solid silver;
background-color: rgba(255, 255, 255, .9);
font-size: 10px;
padding: 0.5em 1em;
border-radius: 4px;
box-shadow: 5px 5px 8px rgba(0, 0, 0, .3);
pointer-events: none; /* ¹ */
}
#balaozinho:empty { /* ² */
display: none;
}
Dois detalhes importantes:
- ¹: queremos que o balão não responda a eventos de mouse para que, quando ele aparecer sob o ponteiro do mouse, a
.marcacao
não lance imediatamente o eventomouseout
(ponteiro saiu da marcação e entrou no balão), acarretando no#balaozinho
ficar piscando (comente essa linha e repare). - ²: quando o
#balaozinho
estiver com seu conteúdo vazio (ie,innerHTML === ''
), ele ficará oculto.
Em script/controles-marcacao.js
, torne possível que o usuário selecione¹ uma .marcacao
clicando nela. A marcação selecionada deve ter a classe selecionada
adicionada a ela (aí fica com a borda amarelinha).
Uma vez selecionada, preencha os campos de section.controles
com os respectivos valores:
- x, y, largura e altura: vêm do
style
da marcação, das propriedadesleft, top, width, height
- Observação: os valores recuperados do
style
do elemento possuem valor e unidade de medida (eg, '200px'). Contudo, ao definir ovalue
dosinput[type="number"]
, deve-se passar apenas o número. Veja como extrair apenas o número de uma string no FAQ.
- Observação: os valores recuperados do
- título, conteúdo e cor: vêm dos atributos de dados da
.marcacao
- formato da marcação: deve-se verificar as classes que a
.marcacao
possui:- Se ela tiver
.formato-oval
, deve-se "checkar" oinput[type="radio"]
cujovalue
éformato-oval
- Senão, checkar o que tem
value="formato-retangular"
- Se ela tiver
¹Selecionar uma marcação: quando alguma .marcacao
sofrer um click
, remova a classe selecionada
de quem é a marcação atual e insira essa mesma classe naquela que foi alvo do click
.
Sugestão de implementação...
Uma ideia é criar uma função atualizaControles(marcacaoEl)
que deve ser chamada sempre que uma .marcacao
é selecionada. Essa função vai preencher os valores de todos os campos informados.
Dentro dela, para cada controle que deve ser preenchido de acordo com a .marcacao
selecionada:
- Pegar uma referência ao campo (
input
) - Definir seu valor (
value
) para o respectivo atributo ou propriedade da.marcacao
Detalhes importantes:
- Campos de posição e tamanho: use
parseInt(el.value)
, comel.style.left|top|width|height
sendo algo como200px
, para extrair apenas a parte numérica antes de definir ovalue
doinput
- Campos de conteúdo e cor: pegue o valor dos atributos de dados e use-os diretamente para definir o
value
doinput
- Botão de rádio do formato:
- Crie uma variável
formato
que conterá a stringformato-oval
ouformato-retangular
verificando se amarcacaoEl.classList.contains('formato-oval')
ou não - Selecione o
input
cujo[value="${formato}"]
e faça-o serchecked
- Crie uma variável
De forma análoga ao exercício anterior, no mesmo arquivo script/controles-marcacao.js
, agora você deve permitir o usuário alterar os controles e isso refletir no estado da .marcacao.selecionada
.
Os campos que devem alterar o estado da .marcacao.selecionada
são os mesmos do exercício anterior. Observe que:
- Campos x, y, largura e altura: devem alterar o
style
da marcação- Lembre-se de concatenar o valor numérico do campo com a unidade de medida
px
- Lembre-se de concatenar o valor numérico do campo com a unidade de medida
- Campos título, conteúdo e cor: definem atributos de dados da marcação
- Botão de rádio de formato: remove as classes de formato e insere novamente apenas aquela cujo botão de rádio está "checkado"
Sugestão de implementação...
Você pode registrar eventos/callbacks específicos para cada campo, ou ser um pouquinho preguiçoso (tudo bem, tá liberado) e criar apenas uma atualizaMarcacao(marcacaoEl)
monstrona que será registrada como callback para todos os input
s e definirá todo o estado da marcação (mesmo que só 1 campo tenha sido alterado).
Para pegar todos os campos que devem ter o evento do tipo input
registrado para essa função, você pode usar o querySelectorAll
com um seletor que pegue "todos os input
exceto aqueles com atributo type="checkbox"
, e também os textarea
(que é só 1). Lembre-se da aula sobre seletores, se quiser fazer um único monstrão desses.
Codifique no arquivo script/controles-foto-anotada.js
.
Para exercitar a propriedade CSS filter
, permita que o usuário escolha um filtro do select#filtro-da-foto
.
Quando o usuário escolher um filtro, simplesmente altere a propriedade filter
da .foto-anotada > img
para o valor da opção escolhida. O FAQ mostra como pegar o valor da opção escolhida em um campo select
.
Código HTML pertinente...
Repare a estrutura do código HTML do select
, que nos permite pegar o value
da option
escolhida e simplesmente definir a propriedade style
da .foto-anotada > img
:
<select id="filtro-da-foto">
<option value="none">nenhum</option>
<option value="sepia(1)">tom de cor sépia</option>
<option value="invert(1)">cores invertidas</option>
<option value="grayscale(1)">escala de cinza</option>
<option value="contrast(8)">super contraste</option>
<option value="blur(3px)">borragem</option>
<option value="hue-rotate(300deg)">rotação de tonalidade</option>
</select>
Há um <input type="file">
que permite ao usuário escolher um arquivo
de seu computador. Faça com que o usuário possa escolher uma imagem para substituir a foto.
Veja o artigo a seguir e tente identificar um código nele que faz o que você precisa: deixa usuário escolher um arquivo e o coloca como uma imagem no lugar da foto dos pokémons.
Referência: https://www.html5rocks.com/en/tutorials/file/dndfiles/
Veja algumas perguntas frequentes ao realizar esta atividade e suas respostas.
Veja os slides sobre alteração de classes. Em suma:
el.classList.add('nome-da-classe'); // coloca
el.classList.remove('nome-da-classe'); // retira
el.classList.toggle('nome-da-classe'); // coloca ou retira
O método classList.toggle()
retorna um booleano indicando se colocou (true
) ou se retirou (false
) a classe do elemento. Por exemplo:
const colocou = el.classList.toggle('nome-da-classe');
if (colocou) {
console.log('.nome-da-classe foi INSERIDA no elemento');
} else {
console.log('.nome-da-classe foi REMOVIDA no elemento');
}
Além disso, ele aceita um segundo parâmetro, booleano, que o força a inserir (se true
) ou a retirar (se false
). Por exemplo:
const inserir = true;
el.classList.toggle('nome-da-classe', inserir);
// vai sempre inserir, mesmo que a classe já esteja lá
Veja os slides de alteração de visibilidade. Dependendo da forma usada, será possível ou não interagir com os elementos (por meio de eventos) mesmo com eles invisíveis. Use a forma mais adequada considerando que o enunciado pede para que não seja possível interagir com as marcações.
Veja os slides sobre como interagir com checkbox via JavaScript. Tendo uma referência a um <input type="checkbox">
, você pode acessar a propriedade checked
, assim:
const el = document....;
console.log(el.checked); // true, se estiver marcado
A mesma lógica vale para saber se um certo botão de rádio (ie, <input type="radio">
) está selecionado.
E, se quiser programaticamente marcar ou desmarcar um checkbox ou radio, vocÊ pode alterar o valor da propriedade checked
para true
/false
, assim:
const el = document....;
el.checked = true; // marcou
Veja os slides sobre como definir o conteúdo de um elemento usando a propriedade innerHTML
.
Veja os slides sobre eventos de mouse.
Veja os slides sobre alteração de estilos via JavaScript. Em suma, usamos a propriedade style
de um elemento HTML e podemos recuperar ou definir o valor de cada propriedade CSS. Lembre-se que os nomes das propriedades CSS, em JavaScript, devem ser "camelCased". Por exemplo:
const el = document....;
el.style.color = 'white';
el.style.backgroundColor = 'black';
el.style.left = '10px';
el.style.position = 'absolute';
Se quiser remover todo o conteúdo de um elemento, você pode definir seu innerHTML
como uma string vazia:
el.innerHTML = '';
Veja os slides sobre como pegar a posição do mouse durante um evento de mousemove
.
Para extrar apenas os números de um string que começa com números, mas tem algum texto depois, você pode usar:
const numeroInteiro = parseInt(texto);
const numeroReal = parseFloat(texto);
Neste exemplo, se texto === '235.2px'
, o parseInt
retorna 235
e o parseFloat
235.2
. Lembre-se, contudo, que seja inteiro ou real, em JavaScript o único tipo de dados numérico se chama Number
e é de ponto flutuante com precisão de 64 bits (equivalente ao double
de C, Java).
Veja os slides sobre como pegar o value
da option
selecionada em um select