I would like to know how to program the JavaScript part that checks whether the onclick function is active - onclick

function myFunction() {
document.getElementById('foo').style.cssText = 'border-color: rgb(0, 0, 255);color: black;font-size: 20px;padding: 0px;margin: 10px 20px';
document.getElementById("demo").innerHTML = "8500$"
}

Related

Applying CSS Class to Canvas

I have a lot of css filter classes that can be applied to an image using the the CSS filter. My goal is to convert the image with the filter applied to dataURL.
To do so, I'm placing the image into a canvas then saving the image after I applied the filter. Here's an example
const img = this.img // my <img />
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
context.filter = 'grayscale(2)'
context.drawImage(img, 0, 0)
const finalImg = canvas.toDataURL()
While this works fine applying a single filter, I have more than 30 filters made in my css class, and I would like to know if there's a way to apply a css class to a canvas object. Worst case scenario is for me to convert all of my filters into an array of string objects, but I'm just very curious. Thanks!
Link for reference to canvas context: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D
You can simply read the value returned by getComputedStyle(canvasElement).filter and use it as your context's filter.
var img=new Image();img.crossOrigin=1;img.onload=draw;
img.src="https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg";
function draw() {
canvas.width = this.width/4; canvas.height = this.height/4;
var ctx = canvas.getContext('2d');
ctx.font = '15px sans-serif';
ctx.fillStyle = 'white';
for(var i=1; i<5; i++) {
// set the class
canvas.className = 'filter' + i;
// retrieve the filter value
ctx.filter = getComputedStyle(canvas).getPropertyValue('filter');
ctx.drawImage(img, 0,0, img.width/4, img.height/4);
ctx.filter = 'none';
ctx.fillText('filter' + i, 20, 20);
// export
canvas.toBlob(saveAsIMG);
}
ctx.drawImage(img, 0,0, img.width/4, img.height/4);
ctx.fillText('canvas - no filter', 20, 20);
}
function saveAsIMG(blob) {
var img = new Image();
img.onload = function(){URL.revokeObjectURL(img.src);};
img.src = URL.createObjectURL(blob);
document.body.appendChild(img);
}
.filter1 {
filter: blur(5px);
}
.filter2 {
filter: grayscale(60%) brightness(120%);
}
.filter3 {
filter: invert(70%);
}
.filter4 {
filter: none;
}
<canvas id="canvas"></canvas>

How does Twitter extract meaningful subject colors from image pixel data?

Let me first clarify the problem statement. Check out this tweet:
https://twitter.com/jungledragon/status/926894337761345538
Next, click the image itself within the tweet. In the light box that appears, the menu bar below it takes on a meaningful color that is based on the actual pixels in the image itself. Even in this stress test, this is a difficult image given all the light pixels, does it do a fine job in picking an overall color that 1) represents the content of the image 2) is dark/contrasty enough to place white text on it:
I was simultaneously implementing a similar system before I even knew Twitter had this. Check out a preview below:
The examples in the screenshot are optimistic, as there are plenty of situations where the background is too light. Even in seemingly positive examples as seen in my screenshot, most of the time it does not pass the AA or AAA contrast check.
My current approach:
One-time per image, a JS runs that calculates the average color of
all pixels in the image. Note that the average color is not
necessarily a meaningful color, such as in the edge case of the
spider where the average would be close to white.
I store the RGB value in the database
Upon rendering the page (server-side) I dynamically set the
background color of the image's caption using a formula
My formula is to convert the RGB to HSL, and then to manipulate in particular the S and L values. Given them a notch, using min/max values to set a treshold. I've tried countless combinations.
Yet it seems like a never-ending struggle because color darkness and contrast are subject to human perception.
Hence my curiosity on how Twitter seems to have nailed this, in particular two aspects:
Finding a meaningful subject color (not the same as average or dominant color)
Toning that meaningful color in a way that it remains recognizable (hue) yet is contrasty enough to place light text on it, whilst passing at least the AA contrast check.
I've searched around, but cannot find any information on their implementation. Anybody aware of they do it? Or other proven methods to solve this puzzle end-to-end?
I took a peek at Twitter's markup to see what I could find, and, after running a bit of code in the browser's console, it seems like Twitter takes a color average over a flat distribution of pixels in the images and scales each of the RGB channels to values of 64 and below. This provides a pretty fast way to create a high-contrast background for light text while still retaining a reasonable color match. From what I can tell, Twitter doesn't perform any advanced subject-color-detection, but I can't say for sure.
Here's a quick-and-dirty demo I made to validate this theory. The top and left borders that appear around the images initially display the color Twitter uses. After running the snippet, a bottom and right border appears with the calculated color. Requires 9+ for IE users.
function processImage(img)
{
var imageCanvas = new ImageCanvas(img);
var tally = new PixelTally();
for (var y = 0; y < imageCanvas.height; y += config.interval) {
for (var x = 0; x < imageCanvas.width; x += config.interval) {
tally.record(imageCanvas.getPixelColor(x, y));
}
}
var average = new ColorAverage(tally);
img.style.borderRightColor = average.toRGBStyleString();
img.style.borderBottomColor = average.toRGBStyleString();
}
function ImageCanvas(img)
{
var canvas = document.createElement('canvas');
this.context2d = canvas.getContext('2d');
this.width = canvas.width = img.naturalWidth;
this.height = canvas.height = img.naturalHeight;
this.context2d.drawImage(img, 0, 0, this.width, this.height);
this.getPixelColor = function (x, y) {
var pixel = this.context2d.getImageData(x, y, 1, 1).data;
return { red: pixel[0], green: pixel[1], blue: pixel[2] };
}
}
function PixelTally()
{
this.totalPixelCount = 0;
this.colorPixelCount = 0;
this.red = 0;
this.green = 0;
this.blue = 0;
this.luminosity = 0;
this.record = function (colors) {
this.luminosity += this.calculateLuminosity(colors);
this.totalPixelCount++;
if (this.isGreyscale(colors)) {
return;
}
this.red += colors.red;
this.green += colors.green;
this.blue += colors.blue;
this.colorPixelCount++;
};
this.getAverage = function (colorName) {
return this[colorName] / this.colorPixelCount;
};
this.getLuminosityAverage = function () {
return this.luminosity / this.totalPixelCount;
}
this.getNormalizingDenominator = function () {
return Math.max(this.red, this.green, this.blue) / this.colorPixelCount;
};
this.calculateLuminosity = function (colors) {
return (colors.red + colors.green + colors.blue) / 3;
};
this.isGreyscale = function (colors) {
return Math.abs(colors.red - colors.green) < config.greyscaleDistance
&& Math.abs(colors.red - colors.blue) < config.greyscaleDistance;
};
}
function ColorAverage(tally)
{
var lightness = config.lightness;
var normal = tally.getNormalizingDenominator();
var luminosityAverage = tally.getLuminosityAverage();
// We won't scale the channels up to 64 for darker images:
if (luminosityAverage < lightness) {
lightness = luminosityAverage;
}
this.red = (tally.getAverage('red') / normal) * lightness
this.green = (tally.getAverage('green') / normal) * lightness
this.blue = (tally.getAverage('blue') / normal) * lightness
this.toRGBStyleString = function () {
return 'rgb('
+ Math.round(this.red) + ','
+ Math.round(this.green) + ','
+ Math.round(this.blue) + ')';
};
}
function Configuration()
{
this.lightness = 64;
this.interval = 100;
this.greyscaleDistance = 15;
}
var config = new Configuration();
var indicator = document.getElementById('indicator');
document.addEventListener('DOMContentLoaded', function () {
document.forms[0].addEventListener('submit', function (event) {
event.preventDefault();
config.lightness = Number(this.elements['lightness'].value);
config.interval = Number(this.elements['interval'].value);
config.greyscaleDistance = Number(this.elements['greyscale'].value);
indicator.style.visibility = 'visible';
setTimeout(function () {
processImage(document.getElementById('image1'));
processImage(document.getElementById('image2'));
processImage(document.getElementById('image3'));
processImage(document.getElementById('image4'));
processImage(document.getElementById('image5'));
indicator.style.visibility = 'hidden';
}, 50);
});
});
label { display: block; }
img { border-width: 20px; border-style: solid; width: 200px; height: 200px; }
#image1 { border-color: rgb(64, 54, 47) white white rgb(64, 54, 47); }
#image2 { border-color: rgb(46, 64, 17) white white rgb(46, 64, 17); }
#image3 { border-color: rgb(64, 59, 46) white white rgb(64, 59, 46); }
#image4 { border-color: rgb(36, 38, 20) white white rgb(36, 38, 20); }
#image5 { border-color: rgb(45, 53, 64) white white rgb(45, 53, 64); }
#indicator { visibility: hidden; }
<form id="configuration_form">
<p>
<label>Lightness:
<input name="lightness" type="number" min="1" max="255" value="64">
</label>
<label>Pixel Sample Interval:
<input name="interval" type="number" min="1" max="255" value="100">
(Lower values are slower)
</label>
<label>Greyscale Distance:
<input name="greyscale" type="number" min="1" max="255" value="15">
</label>
<button type="submit">Run</button> (Wait for images to load first!)
</p>
<p id="indicator">Running...this may take a few moments.</p>
</form>
<p>
<img id="image1" crossorigin="Anonymous" src="https://pbs.twimg.com/media/DNz9fNqWAAAtoGu.jpg:large">
<img id="image2" crossorigin="Anonymous" src="https://pbs.twimg.com/media/DOdX8AGXUAAYYmq.jpg:large">
<img id="image3" crossorigin="Anonymous" src="https://pbs.twimg.com/media/DOYp0HQX4AEWcnI.jpg:large">
<img id="image4" crossorigin="Anonymous" src="https://pbs.twimg.com/media/DOQm1NzXkAEwxG7.jpg:large">
<img id="image5" crossorigin="Anonymous" src="https://pbs.twimg.com/media/DN6gVnpXUAIxlxw.jpg:large">
</p>
The code ignores white, black, and grey-ish pixels when determining the dominant color from the image which gives us a more vivid saturation despite reducing the brightness of the color. The computed color is pretty close to the original color from Twitter for most of the images.
We can improve this experiment by changing which parts of the image we calculate the average color from. The example above selects pixels uniformly across the whole image, but we can try using only pixels near the edges of the image—so the color blends more seamlessly—or we can try averaging color values from the center of the image to highlight the subject. I'll expand on the code and update this answer later when I have some more time.
Something like the example below might be found helpful for what you want to accomplish.
function getAverageColourAsRGB(img) {
var canvas = document.createElement('canvas'),
context = canvas.getContext && canvas.getContext('2d'),
rgb = {
r: 102,
g: 102,
b: 102
},
pixelInterval = 5,
count = 0,
i = -4,
data, length;
if (!context) {
return rgb;
}
var height = canvas.height = img.naturalHeight || img.offsetHeight || img.height,
width = canvas.width = img.naturalWidth || img.offsetWidth || img.width;
context.drawImage(img, 0, 0);
try {
data = context.getImageData(0, 0, width, height);
} catch (e) {
console.error(e);
return rgb;
}
data = data.data;
length = data.length;
while ((i += pixelInterval * 4) < length) {
count++;
rgb.r += data[i];
rgb.g += data[i + 1];
rgb.b += data[i + 2];
}
rgb.r = Math.floor(rgb.r / count);
rgb.g = Math.floor(rgb.g / count);
rgb.b = Math.floor(rgb.b / count);
return rgb;
}
function getContrastYIQ(r, g, b) {
var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
return (yiq >= 128) ? '#000' : '#FFF';
}
function rgb2hex(rgb) {
rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
return (rgb && rgb.length === 4) ? "#" +
("0" + parseInt(rgb[1], 10).toString(16)).slice(-2) +
("0" + parseInt(rgb[2], 10).toString(16)).slice(-2) +
("0" + parseInt(rgb[3], 10).toString(16)).slice(-2) : '';
}
function convertHex(hex) {
hex = hex.replace('#', '');
if (hex.length === 3) {
hex = hex + hex;
}
r = parseInt(hex.substring(0, 2), 16);
g = parseInt(hex.substring(2, 4), 16);
b = parseInt(hex.substring(4, 6), 16);
return [r, g, b];
}
function colorSubH(colorA, colorB) {
rgbA = convertHex(colorA);
rgbB = convertHex(colorB);
c = [];
for (i = 0; i < rgbA.length; i++) {
c.push(parseInt((rgbA[i] + rgbB[i]) / 2));
}
return rgb2hex("rgb(" + c.join(",") + ")");
}
var myImg = document.getElementById("img1");
var color = getAverageColourAsRGB(myImg);
var colorArray = [color.r, color.g, color.b];
var bgColor = rgb2hex("rgb(" + colorArray.join(","));
var txtColor = getContrastYIQ(color.r, color.g, color.b)
var subHColor = colorSubH(txtColor, bgColor);
var footer = document.getElementsByClassName("imgFooter")[0];
footer.style.backgroundColor = bgColor;
footer.style.color = txtColor;
var span = footer.querySelector("span");
span.style.color = subHColor;
.main {
width: 25rem;
height: 100%;
}
img {
width: 100%;
height: auto;
margin-bottom: 0;
}
.main .imgFooter {
position: relative;
height: 2rem;
display: block;
color: #000;
width: 23rem;
bottom: 0;
margin-top: -4rem;
padding: 1rem;
}
<div class="main">
<img id="img1" src="data:image/jpeg;base64,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"
/>
<div class="imgFooter">
Header
<br>
<span>
Sub-header
</span>
</div>
</div>
</div>

Createvector is undefined p5js

UPDATE: Forgot to say, I'm using the p5.js editor. The rest of the sketch works, and loads the image.
When I run this code in the browser I get 'createVector is undefined'. According to the reference for p5.js I'm using the correct syntax.
Any ideas what is wrong?
var img;
var bg = 100;
var velocity = createVector(0,0);
var acceleration = createVector(0,0);
function setup() {
createCanvas(720, 400);
img = loadImage("assets/bb8body.png");
}
function keyPressed() {
if (keyIsPressed === true) {
if (key === 'd') {
acceleration++;
velocity.add(acceleration);
console.log(vx);
}
}
}
function damping(){
}
function draw() {
keyPressed();
background(bg);
image(img, velocity, 0);
damping();
}
I'm not 100% why, but createVector() works in setup() (not outside).
Additionally, I've spotted two small errors:
acceleration++;
This won't work as js doesn't support overloaded operators like c++, but you can use p5.Vector's add() function.
Also, the vx variable doesn't exist in the rest of your code.
Here's a modified version of your code using p5.Vector instances, but drawing a small box instead of the image, for testing purposes. You can run the demo bellow:
var img;
var bg = 100;
var velocity;// = createVector(0,0);
var acceleration;// = createVector(0,0);
function setup() {
createCanvas(720, 400);
velocity = createVector(0,0);
acceleration = createVector(0,0);
//img = loadImage("assets/bb8body.png");
}
function keyPressed() {
if (keyIsPressed === true) {
if (key === 'd') {
//acceleration++;
acceleration.add(1,1);
velocity.add(acceleration);
//console.log(vx);
console.log(velocity.x);
}
}
}
function damping(){
}
function draw() {
keyPressed();
background(bg);
rect(velocity.x,velocity.y,20,20)
//image(img, velocity, 0);
damping();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.22/p5.min.js"></script>

CSS Transitions swap function

I am using CSS transitions to make a slide out panel. I am using Javascript to fire to animation like so;
function slidein()
{
document.getElementById('container').className = 'slidein';
}
function slideout()
{
document.getElementById('container').className = 'slideout';
}
<div onclick="slideout()">Click</div>
What I need to know is once I have fired the first function how can I swap the function so the user can then close the panel.
Try something like this:
var slideState = 'slidein';
function slide() {
if ( slideState == 'slidein' ) slideout();
else slidein();
}
function slidein() {
document.getElementById('container').className = 'slidein';
slideState = 'slidein';
}
function slideout() {
document.getElementById('container').className = 'slideout';
slideState = 'slideout';
}
<div onclick="slide()">Click</div>

How to avoid UpdatePanel scrolling on AutoPostBack?

I have an ASP.NET FormView within an updatepanel. I'm auto-saving the form by setting AutoPostBack=true for each of the items within the FormView.
This means the user can click a few elements in quick succession and fire off a few async postbacks almost simultaneously.
The issue I have is that the user is able to keep scrolling down the form while the async postbacks are not yet complete. The browser always scrolls back to the position it was in at the first postback.
Page.MaintainScrollPositionOnPostback is set to False.
I've tried all sorts of things in ajax and jquery with:
pageLoad
add_initializeRequest
add_endRequest
document.ready
etc..
but I always only seem to be able to access the scroll Y as it was on the first postback.
Is there any way to retrieve the current scroll Y when the postback completes, so I can stop the scrolling occurring? Or perhaps is it possible to disable the scrolling behaviour?
Thanks!
Update
Thanks to #chprpipr, I was able to get this to work. Here's my abbreviated solution:
var FormScrollerProto = function () {
var Me = this;
this.lastScrollPos = 0;
var myLogger;
this.Setup = function (logger) {
myLogger = logger;
// Bind a function to the window
$(window).bind("scroll", function () {
// Record the scroll position
Me.lastScrollPos = Me.GetScrollTop();
myLogger.Log("last: " + Me.lastScrollPos);
});
}
this.ScrollForm = function () {
// Apply the last scroll position
$(window).scrollTop(Me.lastScrollPos);
}
// Call this in pageRequestManager.EndRequest
this.EndRequestHandler = function (args) {
myLogger.Log(args.get_error());
if (args.get_error() == undefined) {
Me.ScrollForm();
}
}
this.GetScrollTop = function () {
return Me.FilterResults(
window.pageYOffset ? window.pageYOffset : 0,
document.documentElement ? document.documentElement.scrollTop : 0,
document.body ? document.body.scrollTop : 0
);
}
this.FilterResults = function (n_win, n_docel, n_body) {
var n_result = n_win ? n_win : 0;
if (n_docel && (!n_result || (n_result > n_docel)))
n_result = n_docel;
return n_body && (!n_result || (n_result > n_body)) ? n_body : n_result;
}
}
Main page:
...snip...
var logger;
var FormScroller;
// Hook up Application event handlers.
var app = Sys.Application;
// app.add_load(ApplicationLoad); - use pageLoad instead
app.add_init(ApplicationInit);
// app.add_disposing(ApplicationDisposing);
// app.add_unload(ApplicationUnload);
// Application event handlers for component developers.
function ApplicationInit(sender) {
var prm = Sys.WebForms.PageRequestManager.getInstance();
if (!prm.get_isInAsyncPostBack()) {
prm.add_initializeRequest(InitializeRequest);
prm.add_beginRequest(BeginRequest);
prm.add_pageLoading(PageLoading);
prm.add_pageLoaded(PageLoaded);
prm.add_endRequest(EndRequest);
}
// Set up components
logger = new LoggerProto();
logger.Init(true);
logger.Log("APP:: Application init.");
FormScroller = new FormScrollerProto();
}
function InitializeRequest(sender, args) {
logger.Log("PRM:: Initializing async request.");
FormScroller.Setup(logger);
}
...snip...
function EndRequest(sender, args) {
logger.Log("PRM:: End of async request.");
maintainScroll(sender, args);
// Display any errors
processErrors(args);
}
...snip...
function maintainScroll(sender, args) {
logger.Log("maintain: " + winScrollTop);
FormScroller.EndRequestHandler(args);
}
I also tried calling the EndRequestHandler (had to remove the args.error check) to see if it reduced flicker when scrolling but it doesn't. It's worth noting that the perfect solution would be to stop the browser trying to scroll at all - right now there is a momentary jitter which would not be acceptable in apps with a large user base.
(The scroll top code is not mine - found it on the web.)
(Here's a helpful MSDN page for the clientside lifecycle: http://msdn.microsoft.com/en-us/library/bb386417.aspx)
Update 7 March:
I just found an extremely simple way to do this:
<script type="text/javascript">
var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_beginRequest(beginRequest);
function beginRequest()
{
prm._scrollPosition = null;
}
</script>
You could bind a function that logs the current scroll position and then reapplies it after each endRequest. It might go something like this:
// Wrap everything up for tidiness' sake
var FormHandlerProto = function() {
var Me = this;
this.lastScrollPos = 0;
this.SetupForm = function() {
// Bind a function to the form's scroll container
$("#ContainerId").bind("scroll", function() {
// Record the scroll position
Me.lastScrollPos = $(this).scrollTop();
});
}
this.ScrollForm = function() {
// Apply the last scroll position
$("#ContainerId").scrollTop(Me.lastScrollPos);
}
this.EndRequestHandler = function(sender, args) {
if (args.get_error() != undefined)
Me.ScrollForm();
}
}
}
var FormHandler = new FormHandlerProto();
FormHandler.Setup(); // This assumes your scroll container doesn't get updated on postback. If it does, you'll want to call it in the EndRequestHandler.
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(FormHandler.EndRequestHandler);
Simply put the Timer control within the content template.
<asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<asp:Timer ID="Timer1" runat="server" Interval="5000" OnTick="Timer1_Tick">
</asp:Timer>
<asp:ImageButton ID="ImageButton1" runat="server" Height="350" Width="700" />
</ContentTemplate>
</asp:UpdatePanel>

Resources