I want to change the opacity of an element while swiping on it.
I would like to achieve an animation similar to the one in the snippet, that is applied gradually depending on how much my finger/cursor has dragged the element while swiping.
EDIT: The animation is the same as clearing a notification in Android
My first idea has been to handle the drag event and change the opacity depending on the position of the element and the width of the screen. Is this a good solution? Is there a better one, maybe CSS only?
I'm using ionic (my element is a ion-item), so anything related to ionic/angular1 could be good too.
div.animated {
width: 100px;
height: 100px;
background: red;
position: absolute;
top: 31px;
animation: right 2s;
animation-iteration-count: infinite;
animation-direction: alternate;
animation-timing-function: linear;
}
.back {
width: 100px;
height: 100px;
border: 1px solid blue;
position: fixed;
top: 30px;
left: 50px;
}
#keyframes right {
0% {
left: 0px;
opacity: 0.1;
}
50% { opacity: 1;}
100% {left: 100px;opacity:0.1}
}
The blue frame is the screen and the red square is the dragged element
<div class="animated"></div>
<div class="back"></div>
The very nice people over at google chrome devs run a show call SuperCharged the idea behind the show is to show you quick and simple ways to make web apps effects.
They did one episode (which is about an hour long) about swipeable cards, they did a quick 10-minute episode just to give you the basic idea.
To answer your question javascript is the only way to make it respond, CSS doesn't respond to user input or action.
Also, it is best to use transform, as opposed to left, when moving things across the screen. They explain the reasons why in great detail during the show but the quick reason is transform can use the GPU.
Anyway here is a live demo of the code they made in the episode, give it a glance and see if it's what you're looking for. I'd recommending watching their videos anyway, you can learn a lot (I certainly did).
/**
Copyright 2016 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
*/
'use strict';
class Cards {
constructor() {
this.cards = Array.from(document.querySelectorAll('.card'));
this.onStart = this.onStart.bind(this);
this.onMove = this.onMove.bind(this);
this.onEnd = this.onEnd.bind(this);
this.update = this.update.bind(this);
this.targetBCR = null;
this.target = null;
this.startX = 0;
this.currentX = 0;
this.screenX = 0;
this.targetX = 0;
this.draggingCard = false;
this.addEventListeners();
requestAnimationFrame(this.update);
}
addEventListeners() {
document.addEventListener('touchstart', this.onStart);
document.addEventListener('touchmove', this.onMove);
document.addEventListener('touchend', this.onEnd);
document.addEventListener('mousedown', this.onStart);
document.addEventListener('mousemove', this.onMove);
document.addEventListener('mouseup', this.onEnd);
}
onStart(evt) {
if (this.target)
return;
if (!evt.target.classList.contains('card'))
return;
this.target = evt.target;
this.targetBCR = this.target.getBoundingClientRect();
this.startX = evt.pageX || evt.touches[0].pageX;
this.currentX = this.startX;
this.draggingCard = true;
this.target.style.willChange = 'transform';
evt.preventDefault();
}
onMove(evt) {
if (!this.target)
return;
this.currentX = evt.pageX || evt.touches[0].pageX;
}
onEnd(evt) {
if (!this.target)
return;
this.targetX = 0;
let screenX = this.currentX - this.startX;
if (Math.abs(screenX) > this.targetBCR.width * 0.35) {
this.targetX = (screenX > 0) ? this.targetBCR.width : -this.targetBCR.width;
}
this.draggingCard = false;
}
update() {
requestAnimationFrame(this.update);
if (!this.target)
return;
if (this.draggingCard) {
this.screenX = this.currentX - this.startX;
} else {
this.screenX += (this.targetX - this.screenX) / 4;
}
const normalizedDragDistance =
(Math.abs(this.screenX) / this.targetBCR.width);
const opacity = 1 - Math.pow(normalizedDragDistance, 3);
this.target.style.transform = `translateX(${this.screenX}px)`;
this.target.style.opacity = opacity;
// User has finished dragging.
if (this.draggingCard)
return;
const isNearlyAtStart = (Math.abs(this.screenX) < 0.1);
const isNearlyInvisible = (opacity < 0.01);
// If the card is nearly gone.
if (isNearlyInvisible) {
// Bail if there's no target or it's not attached to a parent anymore.
if (!this.target || !this.target.parentNode)
return;
this.target.parentNode.removeChild(this.target);
const targetIndex = this.cards.indexOf(this.target);
this.cards.splice(targetIndex, 1);
// Slide all the other cards.
this.animateOtherCardsIntoPosition(targetIndex);
} else if (isNearlyAtStart) {
this.resetTarget();
}
}
animateOtherCardsIntoPosition(startIndex) {
// If removed card was the last one, there is nothing to animate. Remove target.
if (startIndex === this.cards.length) {
this.resetTarget();
return;
}
const frames = [{
transform: `translateY(${this.targetBCR.height + 20}px)`
}, {
transform: 'none'
}];
const options = {
easing: 'cubic-bezier(0,0,0.31,1)',
duration: 150
};
const onAnimationComplete = () => this.resetTarget();
for (let i = startIndex; i < this.cards.length; i++) {
const card = this.cards[i];
// Move the card down then slide it up.
card
.animate(frames, options)
.addEventListener('finish', onAnimationComplete);
}
}
resetTarget() {
if (!this.target)
return;
this.target.style.willChange = 'initial';
this.target.style.transform = 'none';
this.target = null;
}
}
window.addEventListener('load', () => new Cards());
/**
Copyright 2016 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
*/
html,
body {
margin: 0;
padding: 0;
background: #FAFAFA;
font-family: Arial;
font-size: 30px;
color: #333;
}
* {
box-sizing: border-box;
}
.card-container {
width: 100%;
max-width: 450px;
padding: 16px;
margin: 0 auto;
}
.card {
background: #FFF;
border-radius: 3px;
box-shadow: 0 3px 4px rgba(0, 0, 0, 0.3);
margin: 20px 0;
height: 120px;
display: flex;
align-items: center;
justify-content: space-around;
cursor: pointer;
}
<!--
https://github.com/GoogleChrome/ui-element-samples/tree/master/swipeable-cards
https://www.youtube.com/watch?v=rBSY7BOYRo4
/**
*
* Copyright 2016 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-->
<div class="card-container">
<div class="card">Das Surma</div>
<div class="card">Aerotwist</div>
<div class="card">Kinlanimus Maximus</div>
<div class="card">Addyoooooooooo</div>
<div class="card">Gaunty McGaunty Gaunt</div>
<div class="card">Jack Archibungle</div>
<div class="card">Sam "The Dutts" Dutton</div>
</div>
Ansrew's is very useful. In Ionic it is easier to use onDrag and onRelease directives.
<ion-item on-drag="onDrag($event)" on-release="onRelease($event)" />
And use these methods to style the ion-item:
function onDrag (e) {
var element = e.currentTarget.childNodes[0];
var screenW = element.offsetWidth;
var threshold = screenW * 0.16;
var delta = Math.abs(e.gesture.deltaX);
if(delta >= threshold) {
var normalizedDragDistance = (Math.abs(delta) / screenW);
var opacity = 1 - Math.pow(normalizedDragDistance, 0.7);
element.style.opacity = opacity;
} else {
e.currentTarget.childNodes[0].style.opacity = 1;
}
}
function onRelease (e) {
e.currentTarget.childNodes[0].style.opacity = 1;
}
Related
I want to add a timer which decrease Automatically (like 10 seconds, 9 seconds... till 0 seconds) but the progress bar will increase. And I am new to javascript, and the below code also copied from another site , so please help me in adding timer inside the progress bar
Till now I did this code
I want to make like this
Demo
<div class="progress"></div>
<style>
.progress-bar {
height: 20px;
background: #1da1f2;
box-shadow: 2px 14px 15px -7px rgba(30, 166, 250, 0.36);
border-radius: 50px;
transition: all 0.5s;
}
.progress {
width: 100%;
display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: start;
background: #e6e9ff;
border-radius: 20px;
box-shadow: 0px 10px 50px #abb7e9;
}
</style>
<script>
/*
* (class)Progress<nowValue, minValue, maxValue>
*/
//helper function-> return <DOMelement>
function elt(type, prop, ...childrens) {
let elem = document.createElement(type);
if (prop) Object.assign(elem, prop);
for (let child of childrens) {
if (typeof child == "string") elem.appendChild(document.createTextNode(child));
else elem.appendChild(elem);
}
return elem;
}
//Progress class
class Progress {
constructor(now, min, max, options) {
this.dom = elt("div", {
className: "progress-bar"
});
this.min = min;
this.max = max;
this.intervalCode = 0;
this.now = now;
this.syncState();
if(options.parent){
document.querySelector(options.parent).appendChild(this.dom);
}
else document.body.appendChild(this.dom)
}
syncState() {
this.dom.style.width = this.now + "%";
}
startTo(step, time) {
if (this.intervalCode !== 0) return;
this.intervalCode = setInterval(() => {
console.log("sss")
if (this.now + step > this.max) {
this.now = this.max;
this.syncState();
clearInterval(this.interval);
this.intervalCode = 0;
return;
}
this.now += step;
this.syncState()
}, time)
}
end() {
this.now = this.max;
clearInterval(this.intervalCode);
this.intervalCode = 0;
this.syncState();
}
}
let pb = new Progress(15, 0, 100, {parent : ".progress"});
//arg1 -> step length
//arg2 -> time(ms)
pb.startTo(5, 500);
//end to progress after 5s
setTimeout( () => {
pb.end()
}, 10000)
</script>
I think the core problem is that the code you copied is overly complicated especially for beginners. What I would recommend is to start from what you know and build up.
Here is the functionality you want written using only core principles of JavaScript and CSS.
let initialTime = 10; //All time in seconds
let timeLeft = initialTime;
let interval;
let progressBarTextElement = document.getElementById('progress-bar-text');
let progressBarElement = document.getElementById('progress-bar');
function render() {
let progressPercentage = (1 - (timeLeft / initialTime) ) * 100;
progressBarElement.style.width = progressPercentage + '%';
progressBarTextElement.innerHTML = timeLeft + 's';
}
function tick() {
timeLeft = timeLeft - 1;
if(timeLeft <= 0) {
clearInterval(interval); //Stops interval
}
render(); //Updates html
}
function startProgressBar() {
interval = setInterval(tick, 1000); //Will call tick every second
render();
}
startProgressBar();
html {font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif;}
.progress-bar-continer {
height: 80px;
width: 100%;
position: relative;
display: flex;
justify-content: center;
align-items: center;
background-color: #406086;
}
.progress-bar {
background-color: #1b3e80;
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 0%;
transition: width 1s; /* Makes progressBar smooth */
transition-timing-function: linear; /* Try to remove this line for more tick tock feel */
}
.progress-bar-text {
color: white;
font-size: 24px;
font-weight: 700;
position: relative;
z-index: 1;
}
<div class="progress-bar-continer">
<div class="progress-bar" id="progress-bar"></div>
<div class="progress-bar-text" id="progress-bar-text"></div>
<div>
Try to understand the code best you can and you will have no problems adding any features you want on top of it. All the fancy stuff will come later with experience.
So when animating the element, I'm using calc to apply a multiplier (which is a custom property) to the animation duration, and then I change the multiplier in Javscript. The problem is that when the CSS property gets updated the animation is reset and the motion is not smooth. I doubt that there's a fix for this (if I keep using css animation) but hopefully someone will come through.
Here's the code:
function setMultiplier() {
document.documentElement.style.setProperty(
"--animation-multiplier",
Math.random() * 2
);
setTimeout(setMultiplier, 2000);
}
setMultiplier();
:root {
--animation-multiplier: 1;
}
div {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}
#keyframes spin {
50% {
transform: rotate(360deg);
}
100% {
transform: rotate(0deg);
}
}
span {
background: #049;
display: inline-block;
width: 20vh;
height: 20vh;
animation: spin calc(10s / var(--animation-multiplier)) linear infinite;
}
<div>
<span></span>
</div>
EDIT:
Well, this was already supposed to be a 2 day project, so gave up on it and just wrote this shirty code. All these lines to replace 6 lines of css (or JS's animate function. Tried that one too and got the same result):
function animate() {
let sign = 1
const cores = []
for (let i = 1; i <= 11; i++) {
const layer = document.getElementById(`core-layer-${i}`)
const duration = parseInt(layer.getAttribute('data-animation-duration'))
const dpf = 360.0 / (duration / 1000.0) / fps
const opf = (duration / 1000.0) / fps
let animation
if (i === 8) {
animation = null
} else if (i === 9) {
animation = 'opacity'
} else {
animation = 'rotation'
}
sign *= -1
cores.push({
layer: layer,
dpf: dpf * sign,
opf: opf,
animationValue: 0.0,
animation: animation,
alternate: [1, 7].includes(i),
})
}
function tick() {
cores.forEach(core => {
if (core.animation === null) {
return
} else if (core.animation === 'opacity') {
core.animationValue += core.opf * activeAnimationMultiplier
core.layer.style.opacity = Math.sin(core.animationValue / 2)
} else if (core.animation === 'rotation') {
core.animationValue = core.animationValue + core.dpf * activeAnimationMultiplier
core.layer.style.transform = `rotate(${core.animationValue}deg)`
if (core.animationValue > 360.0) {
core.dpf = Math.abs(core.dpf) * -1
} else if (core.animationValue < 0.0) {
core.dpf = Math.abs(core.dpf)
}
}
})
if (Math.abs(activeAnimationMultiplier - animationMultiplier) > 0.05) {
const diff = animationMultiplier - activeAnimationMultiplier
activeAnimationMultiplier += Math.roundf(0.05 * (diff / Math.abs(diff)))
}
setTimeout(tick, 1000.0 / fps)
}
tick()
}
EDIT 2:
Got rid of the JS too. Used Synfig to create the animation (mp4) and played that in a video tag. Results: CPU usage down to ~0.5% from ~2.5% and GPU usage down to ~4% from ~10%.
I am looking into creating a website which will serve as a a digital leaflet for a musical theatre. The idea is to have an autoscrolling credits list as landingpage. I've looked at examples on codepen to see how this effect is been achieved. But I would also like the user to interact and scroll themselves if they want to. When they stop scrolling the credits will turn back to autoscroll. I didn't find any example who tackles this issue. Does someone of you know a script (JS, or plain css…) that can help me with this?
The most straightforward way is to set up a requestAnimationFrame() function and increment the value accordingly, then set the scroll position to it.
Then add the wheel event to detect when a user scrolls (don't use the 'scroll' event though, it already gets called when you change the scrollTop value of the body), also don't forget to cancel the requestAnimationFrame() function. The code would look something like this:
let body = document.body,
starter = document.querySelector("h1"),
scroll_counter = 0,
scrolled,
auto_scroll_kicked = false;
starter.addEventListener("click", start_scrolling);
function start_scrolling() {
auto_scroll_kicked = true;
body.offsetHeight > scroll_counter
? (scroll_counter += 1.12)
: (scroll_counter = body.offsetHeight);
document.documentElement.scrollTop = scroll_counter;
scroller = window.requestAnimationFrame(start_scrolling);
if (scroll_counter >= body.offsetHeight) {
window.cancelAnimationFrame(scroller);
}
}
window.addEventListener("wheel", (e) => {
if (auto_scroll_kicked) {
window.cancelAnimationFrame(scroller);
scroll_counter = 0;
}
});
Play with the codepen if you'd like:
https://codepen.io/SaltyMedStudent/pen/QWqVwaR?editors=0010
There are many options to use: easing functions and etc, but hope this will suffice for now.
In your auto scroll routine before changing position check if previous position is the same as current scrolling position, if it's not - the user scrolled it:
let el = document.documentElement,
footer = document.getElementById("status").querySelectorAll("td"),
scroll_position = 0,
scroll_speed = 0,
scroll_delta = 1.12,
scroller,
status = "stopped";
el.addEventListener("click", scroll);
info();
function scroll(e)
{
if (e.type == "click")
{
window.cancelAnimationFrame(scroller);
scroll_position = el.scrollTop; //make sure we start from current position
scroll_speed++; //increase speed with each click
info("auto scroll");
}
//if previous position is different, this means user scrolled
if (scroll_position != el.scrollTop)
{
scroll_speed = 0;
info("stopped by user");
return;
}
el.scrollTop += scroll_delta * scroll_speed; //scroll to new position
scroll_position = el.scrollTop; //get the current position
//loop only if we didn't reach the bottom
if (el.scrollHeight - el.scrollTop - el.clientHeight > 0)
{
scroller = window.requestAnimationFrame(scroll); //loop
}
else
{
el.scrollTop = el.scrollHeight; //make sure it's all the way to the bottom
scroll_speed = 0;
info("auto stopped");
}
}
function info(s)
{
if (typeof s === "string")
status = s;
footer[1].textContent = el.scrollTop;
footer[3].textContent = scroll_speed;
footer[5].textContent = status;
}
//generate html demo sections
for(let i = 2, section = document.createElement("section"); i < 6; i++)
{
section = section.cloneNode(false);
section.textContent = "Section " + i;
document.body.appendChild(section);
}
//register scroll listener for displaying info
window.addEventListener("scroll", info);
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body
{
font-family: "Roboto", Arial;
user-select: none;
}
section
{
min-height: 100vh;
font-size: 3em;
font-weight: 500;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
}
section:nth-child(even)
{
background: #0b0d19;
}
section:nth-child(odd)
{
background: #131524;
}
#status
{
position: fixed;
bottom: 0;
color: #fff;
margin: 0.5em;
}
#status td:first-of-type
{
text-align: end;
padding-right: 0.4em;
}
#status td:last-of-type
{
font-weight: bold;
}
<section>
Click to start Scrolling
</section>
<table id="status">
<tr><td>position:</td><td></td></tr>
<tr><td>speed:</td><td></td></tr>
<tr><td>status:</td><td></td></tr>
</table>
Whenever i hover over my ext icon, i get the tooltip "Wants access to this site" which is wrong because it should want access only on youtube.com/watch?v=* (and it's another story that Manifest 'match' refuses to accept https://www.youtube.com/watch?v=* as a valid URL)
This is what i'm currently doing:
// manifest.json
{
"name": "YouTube Overlay",
"version": "0.1",
"manifest_version" : 3,
"description": "Lays overlay on YouTube, can be used for guitar chords/lyrics.",
"background" : {
"service_worker" : "bg.js"
},
"action": {},
"permissions": ["activeTab", "scripting"],
"web_accessible_resources": [{
"resources": ["funcsTBInsertedIntoDOM.js"],
"matches": ["https://www.youtube.com/*"]
}]
}
// bg.js
chrome.action.onClicked.addListener(function (tab) {
chrome.scripting.executeScript({
target: {tabId: tab.id},
files: ['yt.js'],
});
});
yt.js when executed above, injects a bunch of HTML elems & CSS rules into DOM. It also injects funcsTBInsertedIntoDOM.js (specified in web_accessible_resources in the manifest.json above) into DOM, which contains function definitions for the injected HTML buttons.
So basically whenever the user clicks on my ext icon, bg.js executes, which in turn executes yt.js. When the user clicks while on a YouTube video, it works fine. But otherwise it throws errors in the console naturally. So how do i instruct the manifest to execute bg.js ONLY on YouTube videos? (it shouldn't even run on other YouTube pages, just only when user is on a video page).
Also, i got a rejection notice from Google Web Store for my extension:
Violation(s):
Use of Permissions:
Violation reference ID: Purple Potassium
Violation: Requesting but not using the following permission(s):
activeTab
How to rectify: Remove the above permission(s)
But if i remove activeTab permission, my extension doesn't work at all.
If someone could propose a solution with both of these problems in mind, i'd be very grateful. Thank you for reading.
Adding additional code to help make it easier:
This is yt.ts:
// all global variables are 'var' instead of let or const because the delete command works only on var
var isFullScreen = false;
var extensionActivated = false;
var resCheckerID:number;
var chordsTALeftPaddingNonFS = chordsTALeftPaddingNonFSOnExit;
var chordsTALeftPaddingFS = "0px";
var thisIsThe1stExitAfterFS = true;
var activateExtension = () => {
console.log("YouTube Overlay activated.");
let scriptElemForASBtn = document.createElement("style");
let innardsForASBtn = styleForAsBtn;
scriptElemForASBtn.innerHTML = innardsForASBtn;
document.head.appendChild(scriptElemForASBtn);
const videoElem = document.querySelector("video");
const vidBottomPanel = document.querySelector(".ytp-chrome-bottom");
const progBarPadding = document.querySelector(".ytp-progress-bar-padding");
const getIdealChordsDivStyles = (isFullScreen:boolean) => {
let vidDims = videoElem.getBoundingClientRect();
let progBarPaddingDims = progBarPadding.getBoundingClientRect();
if (isFullScreen){
console.log("fullscreen detected")
thisIsThe1stExitAfterFS = true;
chordsTALeftPaddingNonFS = chordsTA.style.paddingLeft; // saving this for next nonFS
chordsTA.style.paddingLeft = chordsTALeftPaddingFS; // assigning this from prev FS
return `overflow: hidden; position: absolute; z-index: 1111; width: 100vw; height: ${progBarPaddingDims.y - vidDims.y + (progBarPaddingDims.height/2)}px`;
} else {
try {
if(thisIsThe1stExitAfterFS)
chordsTALeftPaddingFS = chordsTA.style.paddingLeft;
chordsTA.style.paddingLeft = chordsTALeftPaddingNonFS;
thisIsThe1stExitAfterFS = false;
} catch {} // saving this for next FS. on first run it obsly won't be able to find chordsTA.
return `overflow: hidden; position: absolute; z-index: 1111; left: ${vidDims.x}px; top: ${vidDims.y}px; width: ${vidDims.width}px; height: ${progBarPaddingDims.y - vidDims.y + (progBarPaddingDims.height/2)}px`;
}
}
// creating the chords div
let chordsDiv = document.createElement('div');
chordsDiv.style.cssText = getIdealChordsDivStyles(isFullScreen);
chordsDiv.setAttribute("id", "chordsDiv");
let htmlInnards = `
<div id="chordsCtrls" onmouseover="unhideChordsCtrls();" onmouseout="hideChordsCtrls();" style="z-index: 1112; height: ${vidBottomPanel.getBoundingClientRect().height}px; position: absolute; display: inline-block;">
<a id="asBtn" onclick="toggleAutoScroll()" class="btn-flip" data-back="Turn on" data-front="Auto-Scroll Off"></a>
<a id="decTxtSize" class="btn noselect" onclick="decTxtSize();">Tᵀ</a>
<a id="incTxtSize" class="btn noselect" onclick="incTxtSize();">ᵀT</a>
<a id="decIndent" class="btn noselect" onclick="decIndent();">¶-</a>
<a id="incIndent" class="btn noselect" onclick="incIndent();">¶+</a>
</div>
<textarea onkeyup="checkTAWidth();" onclick="checkTAWidth();" id="chordsTA" spellcheck="false" style="position:absolute; left:50%; transform: translate(-50%,0); white-space: pre; overflow-wrap: normal; overflow-x: scroll; font-family: Roboto Mono,monospace; background-color: rgba(0, 0, 0, 0.35); color: white; height: 100%; min-width:10vw; font-size: ${window.screen.height*0.026}px;" placeholder="\n\nPaste\nyour\nchords/lyrics\nin\nhere!">
`
chordsDiv.innerHTML = htmlInnards;
document.body.appendChild(chordsDiv);
chordsTA.value = lyricsOnExit; // doing in convoluted way because i cant fig it out :S
if (chordsTA.value === "undefined") chordsTA.value = "";
chordsTA.scrollTop = lyricsLocOnExit;
chordsTA.style.fontSize = lyricsSizeOnExit;
chordsTA.style.paddingLeft = chordsTALeftPaddingNonFS;
console.log("Lyrics reinstated, if any.");
// hiding the scrollbar of chords div & textarea
let styleForScrollbarHiding = `#chordsDiv::-webkit-scrollbar, #chordsTA::-webkit-scrollbar {height: 0; width: 0;}`;
let styleSheet = document.createElement("style");
styleSheet.type = "text/css";
styleSheet.innerText = styleForScrollbarHiding;
document.head.appendChild(styleSheet);
// auto sizing of chords div
function resCheck() {
let vidDims = videoElem.getBoundingClientRect();
let chordsDims = chordsDiv.getBoundingClientRect();
let requisiteHtForChordsDiv = vidDims.height - vidBottomPanel.getBoundingClientRect().height- (progBarPadding.getBoundingClientRect().height/2);
if (((chordsDims.x !== vidDims.x || chordsDims.width !== vidDims.width) && chordsDims.x !== 0) || (chordsDims.x === 0 && chordsDims.x !== vidDims.x)) { // base cond's latter gets True when exiting from FS. Base's former's former checks in non fullScn mode if x or width is wrong.
if (isFullScreen && vidDims.y === 0) return;
console.log("Video dimensions changed detected, redrawing overlay.");
isFullScreen = vidDims.y === 0 ? true : false;
chordsDiv.style.cssText = getIdealChordsDivStyles(isFullScreen);
}
}
resCheckerID = setInterval(resCheck, 2000);
// addding my JS functions to the youtube HTML webpage/DOM
let s = document.createElement('script');
// TODO: add "scriptName.js" to web_accessible_resources in manifest.json
s.src = chrome.runtime.getURL('funcsTBInsertedIntoDOM.js');
(document.head || document.documentElement).appendChild(s);
}
var styleForAsBtn = `
#asBtn {
opacity: 1;
outline: 0;
color: #FFFFFF;
line-height: 40px;
position: relative;
text-align: center;
letter-spacing: 1px;
display: inline-block;
text-decoration: none;
text-transform: uppercase;
font-size: small;
font-weight: bold;
}
#asBtn:hover:after {
opacity: 1;
transform: translateY(0) rotateX(0);
}
#asBtn:hover:before {
opacity: 0;
transform: translateY(50%) rotateX(90deg);
}
#asBtn:after {
top: 0;
left: 0;
opacity: 0;
width: 100%;
color: #000000;
background: #BCBCBC;
display: block;
transition: 0.25s;
position: absolute;
content: attr(data-back);
transform: translateY(-50%) rotateX(90deg);
}
#asBtn:before {
top: 0;
left: 0;
opacity: 1;
color: #FFFFFF;
background: #323237;
display: block;
padding: 0 5px;
line-height: 40px;
transition: 0.25s;
position: relative;
content: attr(data-front);
transform: translateY(0) rotateX(0);
}
/* CSS for other buttons */
.btn{
display: inline-block;
color: #FFFFFF;
width: 40px;
height: 40px;
line-height: 40px;
border-radius: 50%;
text-align: center;
vertical-align: middle;
overflow: hidden;
font-weight: bold;
background-image: -webkit-linear-gradient(#666666 0%, #323237 100%);
background-image: linear-gradient(#666666 0%, #323237 100%);
text-shadow: 1px 1px 1px rgba(255, 255, 255, 0.66);
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.28);
font-size: x-large;
cursor: pointer;
}
.btn:active{
color: #000000;
}
.btn:hover {
text-align: center;
opacity: 1;
background-image: -webkit-linear-gradient(#999999 0%, #323237 100%);
background-image: linear-gradient(#999999 0%, #323237 100%);
}
.noselect {
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Old versions of Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently
supported by Chrome, Edge, Opera and Firefox */
}
#incTxtSize, #decTxtSize{
font-size: large;
}
#chordsCtrls>*{
transition: transform 0.1s linear;
}
textarea::placeholder {
color: white;
opacity: 0.8;
font-size: 4vh;
}
#contentContainer.tp-yt-app-drawer[swipe-open].tp-yt-app-drawer::after{
visibility: hidden;
}
`
// the last css property above is hiding a thin left side vertical area which otherwise causes chordsCtrls not to show up if mouse is on extreme left. Also causes difficulty clicking SpeedDn button.
if (!document.querySelector("#chordsDiv")){
activateExtension();
} else {
console.log("YouTube Overlay deactivated");
var lyricsOnExit = chordsTA.value;
var lyricsLocOnExit = chordsTA.scrollTop;
var lyricsSizeOnExit = chordsTA.style.fontSize;
var chordsTALeftPaddingNonFSOnExit = chordsTA.style.paddingLeft; // won't be possible to save FS padding unless i deactivate extension with an X btn. Due to scope prob.
document.querySelector("#chordsDiv").remove();
clearInterval(resCheckerID);
delete window.resCheckerID;
}
This is funcsTBInsertedIntoDOM.ts:
console.log("Loading essential funcs needed for YouTube Overlay extension.")
clearInterval(asIntervalID); // cannot clear this from yt.ts because yt.ts runs in a sandbox. So need to clear it here, if it exists, on startup. Thankfully doesn't throw error even if doesn't exist.
clearTimeout(hideChordsCtrlsTimeoutID);
var asSpeeds = {1: 250, 2: 150, 3: 100, 4: 90, 5: 75, 6: 60, 7: 50, 8: 40, 9: 30};
var chordsCtrls:HTMLDivElement = document.querySelector("#chordsCtrls");
var chordsTA:HTMLTextAreaElement = document.querySelector("#chordsTA");
var asBtn:HTMLButtonElement = document.querySelector("#asBtn");
var autoScrollSpeed = 250;
var asIntervalID = 0;
function toggleAutoScroll() {
if(asIntervalID){
clearInterval(asIntervalID);
asIntervalID = 0;
console.log("Stopped autoscroll.");
document.querySelector("#speedUp").remove(); document.querySelector("#speedDn").remove();
setAttributes(asBtn, {"data-front": `Auto-Scroll Off`, 'data-back': 'Turn On'});
return;
}
// create speed + - buttons
let speedUp = document.createElement("a");
speedUp.textContent = "+";
setAttributes(speedUp, {'id': 'speedUp', 'class': 'btn noselect', 'onclick': 'speedUpAS();'});
document.querySelector("#chordsCtrls").insertBefore(speedUp,document.querySelector("#decTxtSize"));
let speedDn = document.createElement("a");
speedDn.textContent = "-";
setAttributes(speedDn, {'id': 'speedDn', 'class': 'btn noselect', 'onclick': 'speedDnAS();'});
document.querySelector("#chordsCtrls").insertBefore(speedDn,asBtn);;
setAttributes(asBtn, {"data-front": `Speed: ${getKeyByValue(asSpeeds,autoScrollSpeed)}`, 'data-back': 'Turn Off'});
asIntervalID = setInterval(_=>{chordsTA.scrollBy(0, 1)}, autoScrollSpeed);
console.log("Started autoscroll.")
}
var speedUpAS = () => {
console.log("Speeding up autoscroll")
let asBtnText = asBtn.getAttribute('data-front');
let newSpeed:number = parseInt(asBtnText.charAt(asBtnText.length - 1))+1;
if (newSpeed in asSpeeds){
clearInterval(asIntervalID);
autoScrollSpeed = asSpeeds[newSpeed];
asIntervalID = 0;
asBtn.setAttribute('data-front', `Speed: ${getKeyByValue(asSpeeds, autoScrollSpeed)}`);
asIntervalID = setInterval(_=>{chordsTA.scrollBy(0, 1)}, autoScrollSpeed);
}
}
var speedDnAS = () => {
console.log("Speeding down autoscroll")
let asBtnText = asBtn.getAttribute('data-front');
let newSpeed:number = parseInt(asBtnText.charAt(asBtnText.length - 1))-1;
if (newSpeed in asSpeeds){
clearInterval(asIntervalID);
autoScrollSpeed = asSpeeds[newSpeed];
asIntervalID = 0;
asBtn.setAttribute('data-front', `Speed: ${getKeyByValue(asSpeeds, autoScrollSpeed)}`);
asIntervalID = setInterval(_=>{chordsTA.scrollBy(0, 1)}, autoScrollSpeed);
}
}
var incTxtSize = () => {
let currFontSize = parseFloat(chordsTA.style.fontSize);
let newFontSize = currFontSize += 1;
chordsTA.style.fontSize = `${newFontSize}px`;
qickSizeUp();
}
var decTxtSize = () => {
let currFontSize = parseFloat(chordsTA.style.fontSize);
let newFontSize = currFontSize -= 1;
chordsTA.style.fontSize = `${newFontSize}px`;
qickSizeDn();
}
var unhideChordsCtrls = () => {
clearTimeout(hideChordsCtrlsTimeoutID);
let childrenOfchordsCtrlsDiv:any = chordsCtrls.getElementsByTagName("*");
for (let index = 0; index < childrenOfchordsCtrlsDiv.length; index++) {
childrenOfchordsCtrlsDiv[index].style.transform = "translate(0,0)";
}
}
var hideChordsCtrlsTimeoutID = 0;
var hideChordsCtrls = () => {
hideChordsCtrlsTimeoutID = setTimeout(() => {
let childrenOfchordsCtrlsDiv:any = chordsCtrls.getElementsByTagName("*");
for (let index = 0; index < childrenOfchordsCtrlsDiv.length; index++) {
childrenOfchordsCtrlsDiv[index].style.transform = "translate(0,-100%)";
}
}, 2000);
}
hideChordsCtrlsTimeoutID = setTimeout(() => { //hide the controls after initially showing them for 4 secs
hideChordsCtrls();
}, 4000);
var decIndent = () => {
let newLeftPadding = (parseInt(chordsTA.style.paddingLeft) - 50);
if (!newLeftPadding) newLeftPadding = 0; // this catches NaN on first run, as it is not set. Also doesn't allow to go less than 0 somehow, luckily.
chordsTA.style.paddingLeft = `${newLeftPadding}px`;
}
var incIndent = () => {
let newLeftPadding = (parseInt(chordsTA.style.paddingLeft) + 50);
if (!newLeftPadding) newLeftPadding = 50; // this catches NaN on first run, as it is not set.
if (newLeftPadding > document.querySelector("#chordsDiv").getBoundingClientRect().width) return;
chordsTA.style.paddingLeft = `${newLeftPadding}px`;
}
// following funcs stolen from SO for finding a key by its value & setting attributes multiple at a time.
function getKeyByValue(object:object, value:Number) {
return Object.keys(object).find(key => object[key] === value);
}
function setAttributes(el:HTMLElement, attrs:object) {
for(var key in attrs) {
el.setAttribute(key, attrs[key]);
}
}
Or you may prefer to read the code over at GitHub: https://github.com/XtremePwnership/YoutubeOverlay
Since YouTube video pages are hosted at youtube.com/watch, specifying that in your manifest is the way to go:
{
"name": "YouTube Overlay",
"version": "0.1",
"manifest_version" : 3,
"description": "Lays overlay on YouTube, can be used for guitar chords/lyrics.",
"background" : {
"service_worker" : "bg.js"
},
"action": {},
"permissions": ["activeTab", "scripting"],
"web_accessible_resources": [{
"resources": ["funcsTBInsertedIntoDOM.js"],
"matches": ["https://www.youtube.com/watch?v=*"]
}]
}
I am creating a learning module for an education company where i create 25 animal sprites (canvas with an image in it) and put them in a farm (div with a background image). I then reorder their z-index according to their location on the background, so that closer sprites will be on top of farther ones (both the background DIV and the sprites are position:absolute;).
This is the function that rearranges the sprites:
Array.prototype.sortIndices = function (func) {
var i = j = this.length,
that = this;
while (i--) {
this[i] = { k: i, v: this[i] };
}
this.sort(function (a, b) {
return func ? func.call(that, a.v, b.v) :
a.v < b.v ? -1 : a.v > b.v ? 1 : 0;
});
while (j--) {
this[j] = this[j].k;
}
}
function rearrangeSprites() {
var zSprites = new Array();
for (var i = 0; i < sprites.length; i++) {
var a = $('#sprite_'+i).css('bottom');
a = a.substr(0, a.length - 2);
zSprites[i] = { b : -a*1 };
}
zSprites.sortIndices(function(a,b) { return a.b - b.b; });
for (var i = 0; i < zSprites.length; i++) {
spriteObjects[zSprites[i]].style.zIndex = (1001 + i) + '';
}
}
It works great in IE and Firefox, but Chrome doesn't respect the z-index order.
any ideas?
Response to answers:
justspamjustin: Tried negative z-indices, as the article seemed to note, at some point. also tried reordering the objects, using this code:
$('.sprite').detach();
for (var i = 0; i < zSprites.length; i++) {
$('#Stage_udi_meadow').append(spriteObjects[zSprites[i]]);
spriteObjects[zSprites[i]].style.zIndex = (i + 1000) + '';
}
nada!
Francis: it would be quite a thing to replace the canvases with, say... DIVs, as a lot of code is built around the canvas features. I also need it to be canvases, because i am using transparency, PNG shadows and doing hit tests for the drag, which will not work with a simple DIV, so I will save this delicious option for last.
apsillers: CSS (as requested):
for the sprites:
element.style {
width: 60.674351585014406px;
height: 60.674351585014406px;
left: 204.55043227665706px;
top: 22.550432276657062px;
cursor: pointer;
z-index: 1003;
}
.sprite {
background-repeat: no-repeat;
margin: 0px;
padding: 0px;
overflow: hidden;
position: absolute;
z-index: 140;
}
.EDGE-122375087, .EDGE-122375087 * {
-webkit-transform: translateX(0px);
}
for the background:
element.style {
position: absolute;
margin: 0px;
left: 0px;
top: 177px;
width: 566px;
height: 347px;
right: auto;
bottom: auto;
background-size: 100%;
background-image: url(http://localhost:9090/cet_html5/publish/images/udi_meadow.png);
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
opacity: 1;
background-position: 0px 0px;
background-repeat: no-repeat no-repeat;
}
#Stage_udi_meadow {
}
.EDGE-122375087, .EDGE-122375087 * {
-webkit-transform: translateX(0px);
}
user agent stylesheetdiv {
display: block;
}
Inherited from body
Style Attribute {
cursor: auto;
}
Sometimes z-index can be a bit tricky. This article from the W3 may be of some help. But that spec may be a bit confusing. If I can't get z-index to work, then I make sure that my elements in the DOM are ordered properly. Generally elements lower in the DOM, have a higher visibility preference. So under some conditions, this might be true:
<div style="z-index:9999">I'm on bottom</div>
<div>I'm on top</div>
Try reordering the elements in the DOM.