Javascript: Memory efficient way to do this effect - javascript

I wrote the code in Javascript but any good alternative would do.
EFFECT: onmousemove over the webpage circles of random colors should create wherever the mouse moves. and they have to be added behind a mask image(circles are visible only in the transparent portion of the image which is a logo. thus creating a color paint to create logo onmousemove.
it doesn't work in jsfidde because of its memory intensiveness.
WORKING LINK: http://goo.gl/DNRxO9
I pasted the exact code you can create a new html file with the following code and IT WORKS PERFECT IN FIREFOX ONLY because of its memory intensiveness(lots of divs added in very short time so DOM becomes very very heavy).
<html>
<head>
<style type="text/css">
body{
padding:0;
margin:0;
overflow: hidden;
}
#mask{
width:100%;
height:auto;
position:absolute;
z-index:10;
}
#logo{
width:50%;
height:50%;
margin:auto;
}
.point{
width:0px;
height:0px;
background-color:#ff0000;
position:absolute;
z-index:5;
left:50px;top:50px;
border-width:50px;
border-style: solid;
border-color:red;
border-radius:50px;
opacity:1;
transition: border-width 3s ease-in-out;
}
.no-border{border-width:0px;}
</style>
<script type="text/javascript">
function getRandomColor() {
var letters = '0123456789ABCDEF'.split('');
var color = '#';
for (var i = 0; i < 6; i++ ) {
color += letters[Math.round(Math.random() * 15)];
}
return color;
}
/* OptionalCode: for removing divs after a lot are created */
Element.prototype.remove = function() {
this.parentElement.removeChild(this);
}
NodeList.prototype.remove = HTMLCollection.prototype.remove = function() {
for(var i = 0, len = this.length; i < len; i++) {
if(this[i] && this[i].parentElement) {
this[i].parentElement.removeChild(this[i]);
}
}
}
i=0;
function colors(event){
var x=event.clientX;
var y=event.clientY;
var point = document.getElementsByClassName('point');
document.body.innerHTML += "<div class='point'></div>";
point[i].style.borderColor = getRandomColor();
//point[i].className += ' no-border';
point[i].style.left = x + 'px';
point[i].style.top = y + 'px';
i++;
}
function position(){
var ht = window.getComputedStyle(document.getElementById("mask"), null).getPropertyValue("height");
var ht_num = Number(ht.slice(0,ht.length - 2));
margin_top = (Number(document.body.clientHeight) - ht_num)/2;
document.getElementById('mask').style.marginTop = margin_top + "px";
}
</script>
</head>
<body onload="position();" onmousemove="colors(event)">
<img id="mask" src="http://goo.gl/EqfJ0L">
</body>
</html>

There is one HUGE, HUGE, HUGE performance killer in your code:
document.body.innerHTML += "<div class='point'></div>";
This takes your entire document, throws it away and just inserts everything back again. This is horrible! Remember this for all times and never do this again! ;)
Keep the basic rule in mind, to never add Elements via .innerHTML!
The correct way to go is the following:
// create your new div element
var circleElement = document.createElement("div");
// add all the stuff needed
circleElement.classList.add("point");
circleElement.style.borderColor = getRandomColor();
circleElement.style.left = x + 'px';
circleElement.style.top = y + 'px';
// now append the element to the body
document.body.appendChild(circleElement);
This creates a single div and nicely inserts it as a child-element of the body.
Additionally you can decrease the number of divs drawn by introducing a threshhold:
var lastX=0,lastY=0;
function colors(event){
var x=event.clientX;
var y=event.clientY;
if (Math.abs(lastX - x) + Math.abs(lastY - y) <= 10 ) return;
/* do stuff */
lastX = x;lastY = y;
}
As a third measure you can decrease the size of the image to just hold the mask element and trigger the mousemove only on the image (because divs outside the mask are hidden anyway).
Ultimately, you could kill "old" div-elements when you have reached a certain amount.
I have not included these two last optimizations, but look at the already supersmooth example now!

Related

Unable to generate multiple Images

This is a game where 5 more images is to be added every time when a user click on last node of left side div.
If he clicks on some other node alert 'game over' should be displayed.
Issues:
TypeError: leftside.lastChild is null
How would I delete all nodes of both the right and left hand side div before calling the generateface function to generate new set of faces.
Matching Game
click on the extra smiling face on the left
.smile {position:absolute}
div {position:absolute; width:500px;height: 500px}
#rightside {left:500px;border-left: 1px solid black}
var numberOfFaces = 5;
var leftside = document.getElementById("leftside");
var rightside = document.getElementById("rightside");
function generatefaces() {
for (i=0;ievent.stopPropagation();
numberOfFaces += 5;
generateFaces();
};
theBody.onclick = function gameOver() {
theBody.onclick = null;
alert("Game Over!");
};
You forgot to add px to the top and left style property.
Change the code as below
ismile.style.top = topran + 'px';
ismile.style.left = leftran + 'px';
var numberOfFaces = 5;
var leftside = document.getElementById("leftside");
function generatefaces() {
for (i=0;i<=numberOfFaces;i++) {
ismile = document.createElement("img");
ismile.src = "http://orig00.deviantart.net/449c/f/2009/061/5/7/smiley_icon_by_emopunk23.jpg";
ismile.className = "smile";
var topran = Math.random() * 400;
topran = Math.floor(topran);
var leftran = Math.random() *400;
leftran = Math.floor(leftran);
ismile.style.top = topran + 'px';
ismile.style.left = leftran + 'px';
leftside.appendChild(ismile);
}
}
.smile {position:absolute}
div {position:absolute; width:500px;height: 500px}
#rightside {left:500px;border-left: 1px solid black}
<body onload="generatefaces()">
<h1> Matching Game </h1>
<p> click on the extra smiling face on the left </p>
<div id="leftside"></div>
<div id="rightside"></div>
</body>

Add a transform value to the current transforms that are already on the element?

Let's say I have a div that had translateX and translateY values added dynamically.
<div class="object child0"
style="-webkit-transform: translateX(873.5px) translateY(256px);
width: 50px; height: 50px;">
I want to add rotateY(20deg) to the current transforms, but applying it via
element.style.webkitTransform = "rotateX(20deg)" loses the other values.
Is there a way to add the rotateY without losing the translateX and translateY transforms?
You could use the += operator to append the rotateX(20deg) to the already existing transformation.
el.style.webkitTransform += "rotateX(20deg)";
Note: I have used a different transformation in the below snippet for the visual effect but method is the same.
window.onload = function() {
var el = document.getElementsByTagName("div")[0];
el.style.webkitTransform += "rotateZ(20deg)";
console.log(el.style.webkitTransform);
document.getElementById("changeDeg").onclick = changeDeg; //event handler
}
function changeDeg() {
var el = document.getElementsByTagName("div")[0];
var re = /(rotateZ)(\(.*(?:deg\)))/g; //regex to match rotateZ(...deg)
var newDeg = 40;
if (el.style.webkitTransform.match(re).length != -1) {
el.style.webkitTransform = el.style.webkitTransform.replace(re, '$1(' + newDeg + 'deg)'); // $1 is first capturing group which is "rotateZ"
}
console.log(el.style.webkitTransform);
}
div {
background: red;
margin-bottom: 20px;
}
<div class="display-object child0" style="-webkit-transform: translateX(43.5px) translateY(6px); width: 50px; height: 50px;"></div>
<button id="changeDeg">Change Rotation</button>
I know this topic is a bit old, and there is a great answer above by Harry.
Though here is an addition in case you need to modify the transform again:
It turns out that that css converts the transform string into a matrix, which makes it extremely hard to understand how to modify (Here is a full documentary).
So string manipulation solutions are the shortest. Here are the good news:
You actually can "stack" multiple transformations!
Try this:
let el = document.getElementsByTagName("div")[0];
// first, sync JS transform with current transform style:
const originalMatrix = window.getComputedStyle(el).transform
if(!el.style.transform){
el.style.transform = originalMatrix
}
// Then apply as much transforms as you whish
let myTransform = " rotate(45deg)"
el.style.transform += myTransform
el.style.transform += myTransform
The div will rotate 90 degrees!
for that to work make sure that the element have some transform declatation beforehand (one can use * {"transform: scale(1)"} in the style sheet)
Now What if you whish to revert all your changes?
const originalMatrix = window.getComputedStyle(el).transform
// Do some changes....
el.style.setProperty('transform', originalMatrix)
Finally, here is a working example:
Click on the div to modify it, or on the body to revert it to original:
window.onload= () => {
let el = document.getElementById("myDiv")
const originalMatrix = window.getComputedStyle(el).transform
document.addEventListener('click', e=>{
if(e.target != el){return revert(el)}
// Sync El transform style
if(!el.style.transform){
el.style.transform = originalMatrix
}
// Aplly some more transforms
el.style.transform = el.style.transform
let myTransform = " translate(20px, 20px) rotate(45deg)"
el.style.transform += myTransform
})
function revert(el){
el.style.setProperty('transform', originalMatrix)
}
}
div{
background:green;
height:50px;
width:100px;
transform:translate(50px, 50px);
transition:1s;
}
<body>
Click body to revert him
<div id="myDiv">ClickMe</div>
</body>

Gradient only on one section

On my previous post I asked how I'd get the gradient set up. Now the problem is that the gradient "spreads" out. Here's What I'm using
function generateCSSGradient(colours) {
var l = colours.length, i;
for( i=0; i<l; i++) colours[i] = colours[i].join(" ");
return "linear-gradient( to right, "+colours.join(", ")+")";
}
var cols = [
["red","0%"],
["red","40%"],
["yellow","40%"],
["yellow","60%"],
["green","60%"],
["green","80%"]
];
yourElement.style.background = generateCSSGradient(cols);
With this. What I want to do is say you fill in one input. And the bar goes to 33%, then that could be a red color. Then the next would be a blue and so fourth. Not like this. Any ideas? I'd also avoid using div
I think you want it like this ... See the source code
HTML
I'v edited the HTML code and added another div called colors inside the div top ...
<div class="top">
<div class="colors"></div>
</div>
CSS
Also I edited the CSS of .top and added to it overflow:hidden; and create .colors style
.top{
/*background: #009dff;*/
background:linear-gradient(to right,#009dff 0,#00c8ff 100%);
position:fixed;
z-index:1031;
top:0;
left:0;
height:4px;
transition:all 1s;
width:0;
overflow: hidden;
}
.colors{
width: 100%;
height: 4px;
}
JavsScript
Then edited the JavaScript and made the CSSGradient to colors not top , and let the JavaScript set the width of colors to fit the window width , and changed the colors percentage..
document.querySelector(".colors").style.background = generateCSSGradient(cols);
var window_width = window.innerWidth + "px";
document.querySelector(".colors").style.width = window_width;
var cols = [
["red","0%"],
["red","33.3%"],
["yellow","33.3%"],
["yellow","66.6%"],
["green","66.6%"],
["green","100%"]
];
Hope this will help you ...
Update
if you want to change the color of the bar like this , See the source code ...
just edit the JavaScript to be like this
function cback(e) {
var t = [];
for (var n = inputs.length; n--;) {
if (!inputs[n].value.length) t.push(inputs[n]);
}
var r = t.length;
var i = inputs.length;
var s = document.querySelectorAll(".top");
for (var o = s.length; o--;) {
s[o].style.width = 100 - r / i * 100 + "%";
s[o].style.background = cols[i-r-1];
}
}
var forms = document.querySelectorAll(".form"),
inputs = [];
for (var i = forms.length; i--;) {
var els = forms[i].querySelectorAll("input, textarea, select");
for (var j = els.length; j--;) {
if (els[j].type != "button" && els[j].type != "submit") {
inputs.push(els[j]);
els[j].addEventListener("input", cback, false);
}
}
}
var cols = ["red","yellow","green"];

Get only the ellipsis text using jquery

Nice code, just wondered if it is possible to query and get the ellipsis text (i.e. with the dots in and not the original text)?
If I add the text
This is a long sentence
and (using the relevant css for ellipsis) it gets shortened to
This is a long sen ...
Is there a way to get the text
"This is a long sen ..."
from the $("p") DOM object rather than the original text?
Try that:
function getEllipsis(command, characters) {
for (var i = command.length; i >= 0; i--) {
if (command.substring(0, i).length < characters) {
if (i < command.length) {
command = command.substring(0, i) + "...";
}
return command;
}
}
}
console.log(getEllipsis("I am a long sentence",16))
console.log(getEllipsis("But I am even longer",20))
I have a rough draft that needs some browser-specific tweaking.
JavaScript:
jQuery.fn.getShowingText = function () {
// Add temporary element for measuring character widths
$('body').append('<div id="Test" style="padding:0;border:0;height:auto;width:auto;position:absolute;display:none;"></div>');
var longString = $(this).text();
var eleWidth = $(this).innerWidth();
var totalWidth = 0;
var totalString = '';
var finished = false;
var ellipWidth = $('#Test').html('…').innerWidth();
var offset = 7; // seems to differ based on browser (6 for Chrome and 7 for Firefox?)
for (var i = 0;
(i < longString.length) && ((totalWidth) < (eleWidth-offset)); i++) {
$('#Test').text(longString.charAt(i));
totalWidth += $('#Test').innerWidth();
totalString += longString.charAt(i);
if(i+1 === longString.length)
{
finished = true;
}
}
$('body').remove('#Test'); // Clean up temporary element
if(finished === false)
{
return totalString.substring(0,totalString.length-3)+"…";
}
else
{
return longString;
}
}
console.log($('#ellDiv').getShowingText());
CSS:
#Test {
padding:0;
border:0;
height: auto;
width: auto;
position:absolute;
white-space: pre;
}
div {
width: 100px;
white-space: nowrap;
border: 1px solid #000;
overflow:hidden;
text-overflow:ellipsis;
padding:0;
}
With the caveat that the offset needs to change depending on the browser, unless someone can figure out what is causing it.
I suspect letter-spacing or similar?

How can I know when there is a new line on a Wheel of Fortune Board?

I the following code here in which you can play a Wheel of Fortune-like game with one person (more of my test of javascript objects).
My issue is that when the screen is small enough, the lines do not seem to break correctly.
For example:
Where the circle is, I have a "blank" square. The reason why I have a blank square is so that when the screen is big enough, the square serves as a space between the words.
Is there a way in my code to efficiently know if the blank square is at the end of the line and to not show it, and then the window gets resized, to show it accordingly?
The only thought I had was to add a window.onresize event which would measure how big the words are related to how big the playing space is and decide based on that fact, but that seems very inefficient.
This is my code for creating the game board (starts # line 266 in my fiddle):
WheelGame.prototype.startRound = function (round) {
this.round = round;
this.lettersInPuzzle = [];
this.guessedArray = [];
this.puzzleSolved = false;
this.currentPuzzle = this.puzzles[this.round].toUpperCase();
this.currentPuzzleArray = this.currentPuzzle.split("");
var currentPuzzleArray = this.currentPuzzleArray;
var lettersInPuzzle = this.lettersInPuzzle;
var word = document.createElement('div');
displayArea.appendChild(word);
word.className = "word";
for (var i = 0; i < currentPuzzleArray.length; ++i) {
var span = document.createElement('div');
span.className = "wordLetter ";
if (currentPuzzleArray[i] != " ") {
span.className += "letter";
if (!(currentPuzzleArray[i] in lettersInPuzzle.toObject())) {
lettersInPuzzle.push(currentPuzzleArray[i]);
}
word.appendChild(span);
} else {
span.className += "space";
word = document.createElement('div');
displayArea.appendChild(word);
word.className = "word";
word.appendChild(span);
word = document.createElement('div');
displayArea.appendChild(word);
word.className = "word";
}
span.id = "letter" + i;
}
var clear = document.createElement('div');
displayArea.appendChild(clear);
clear.className = "clear";
};
Instead of JavaScript, this sounds more like a job for CSS, which solves this problem all the time when dealing with centered text.
Consider something like this:
CSS
#board {
text-align: center;
border: 1px solid blue;
font-size: 60pt;
}
.word {
display: inline-block;
white-space: nowrap; /* Don't break up words */
margin: 0 50px; /* The space between words */
}
.word span {
display: inline-block;
width: 100px;
border: 1px solid black
}
HTML
<div id="board">
<span class="word"><span>W</span><span>h</span><span>e</span><span>e</span><span>l</span></span>
<span class="word"><span>o</span><span>f</span></span>
<span class="word"><span>F</span><span>o</span><span>r</span><span>t</span><span>u</span><span>n</span><span>e</span></span>
</div>
Here's a fiddle (try resizing the output pane).
Here you go. Uses the element.offsetTop to determine if a .space element is on the same line as its parent.previousSibling.lastChild or parent.nextSibling.firstChild.
Relevant Code
Note: In the fiddle I change the background colors instead of changing display so you can see it work.
// hides and shows spaces if they are at the edge of a line or not.
function showHideSpaces() {
var space,
spaces = document.getElementsByClassName('space');
for (var i = 0, il = spaces.length ; i < il; i++) {
space = spaces[i];
// if still display:none, then offsetTop always 0.
space.style.display = 'inline-block';
if (getTop(nextLetter(space)) != space.offsetTop || getTop(prevLetter(space)) != space.offsetTop) {
space.style.display = 'none';
} else {
space.style.display = 'inline-block';
}
}
}
// navigate to previous letter
function nextLetter(fromLetter) {
if (fromLetter.nextSibling) return fromLetter.nextSibling;
if (fromLetter.parentElement.nextSibling)
return fromLetter.parentElement.nextSibling.firstChild;
return null;
}
// navigate to next letter
function prevLetter(fromLetter) {
if (fromLetter.previousSibling) return fromLetter.previousSibling;
if (fromLetter.parentElement.previousSibling)
return fromLetter.parentElement.previousSibling.lastChild;
return null;
}
// get offsetTop
function getTop(element) {
return (element) ? element.offsetTop : 0;
}
showHideSpaces();
if (window.addEventListener) window.addEventListener('resize', showHideSpaces);
else if (window.attachEvent) window.attachEvent('onresize', showHideSpaces);
jsFiddle

Categories

Resources