I am trying to create a parallax background effect with some knockout text. I am facing two issues:
The parallax effect does not work at all when background-size: contain; is set.
The parallax effect, for whatever reason, does not work outside of Codepen - when scaled up fully in chrome, the effect disappears and behaves exactly as if the background is stuck to your mouse...I am not sure if this is a scaling issue or issue with chrome and my method.
Here is my code:
(function() {
// Add event listener
document.addEventListener("mousemove", parallax);
const elem = document.querySelector("#text");
// Magic happens here
function parallax(e) {
let _w = window.innerWidth / 2;
let _h = window.innerHeight / 2;
let _mouseX = e.clientX;
let _mouseY = e.clientY;
let _depth1 = `${50 - (_mouseX - _w) * 0.01}% ${50 - (_mouseY - _h) * 0.01}%`;
let _depth2 = `${50 - (_mouseX - _w) * 0.02}% ${50 - (_mouseY - _h) * 0.02}%`;
let _depth3 = `${50 - (_mouseX - _w) * 0.06}% ${50 - (_mouseY - _h) * 0.06}%`;
let x = `${_depth3}, ${_depth2}, ${_depth1}`;
console.log(x);
elem.style.backgroundPosition = x;
}
})();
.background {
background: #000;
}
#text {
background: url("https://images.unsplash.com/photo-1465101162946-4377e57745c3?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1957&q=80");
/*background-size: contain;*/
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
font-size: 8vw;
}
<body class="background">
<h1 id="text"><b>Galaxy</b></h1>
</body>
The solution turned out to be that it depends on the size of the image, which it seems must be smaller than the container (in this case the entire screen/body) in order for this to work.
The image I was using in the above codepen was different to the image I was using in my local instance. To make it behave correctly, I simply applied background-size: 50%; to my much larger local background image so that it behaves more like the smaller background image used in the codepen.
Related
I'm trying to redo the animation that I saw on a site where an image changes it's x and y values with the movement of the mouse. The problem is that the origin of the mouse is in the top left corner and I'd want it to be in the middle.
To understand better, here's how the mouse axis values work :
Now here's how I'd want it to be:
sorry for the bad quality of my drawings, hope you understand my point from those ^^
PS: I'm having a problem while trying to transform the x y values at the same time and I don't know why.
Here's what I wrote in JavaScript :
document.onmousemove = function(e){
var x = e.clientX;
var y = e.clientY;
document.getElementById("img").style.transform = "rotateX("+x*0.005+"deg)";
document.getElementById("img").style.transform = "rotateY("+y*0.005+"deg)";
}
The exact 3D effect you're up to is called "tilting".
Long story short, it uses CSS transform's rotateX() and rotateY() on a child element inside a perspective: 1000px parent. The values passed for the rotation are calculated from the mouse/pointer coordinates inside the parent Element and transformed to a respective degree value.
Here's a quick simplified remake example of the original script:
const el = (sel, par) => (par || document).querySelector(sel);
const elWrap = el("#wrap");
const elTilt = el("#tilt");
const settings = {
reverse: 0, // Reverse tilt: 1, 0
max: 35, // Max tilt: 35
perspective: 1000, // Parent perspective px: 1000
scale: 1, // Tilt element scale factor: 1.0
axis: "", // Limit axis. "y", "x"
};
elWrap.style.perspective = `${settings.perspective}px`;
const tilt = (evt) => {
const bcr = elWrap.getBoundingClientRect();
const x = Math.min(1, Math.max(0, (evt.clientX - bcr.left) / bcr.width));
const y = Math.min(1, Math.max(0, (evt.clientY - bcr.top) / bcr.height));
const reverse = settings.reverse ? -1 : 1;
const tiltX = reverse * (settings.max / 2 - x * settings.max);
const tiltY = reverse * (y * settings.max - settings.max / 2);
elTilt.style.transform = `
rotateX(${settings.axis === "x" ? 0 : tiltY}deg)
rotateY(${settings.axis === "y" ? 0 : tiltX}deg)
scale(${settings.scale})
`;
}
elWrap.addEventListener("pointermove", tilt);
/*QuickReset*/ * {margin:0; box-sizing: border-box;}
html, body { min-height: 100vh; }
#wrap {
height: 100vh;
display: flex;
background: no-repeat url("https://i.stack.imgur.com/AuRxH.jpg") 50% 50% / cover;
}
#tilt {
outline: 1px solid red;
height: 80vh;
width: 80vw;
margin: auto;
background: no-repeat url("https://i.stack.imgur.com/wda9r.png") 50% 50% / contain;
}
<div id="wrap"><div id="tilt"></div></div>
Regarding your code:
Avoid using on* event handlers (like onmousemove). Use EventTarget.addEventListener() instead — unless you're creating brand new Elements from in-memory. Any additionally added on* listener will override the previous one. Bad programming habit and error prone.
You cannot use style.transform twice (or more) on an element, since the latter one will override any previous - and the transforms will not interpolate. Instead, use all the desired transforms in one go, using Transform Matrix or by concatenating the desired transform property functions like : .style.transform = "rotateX() rotateY() scale()" etc.
Disclaimer: The images used in the above example from the original problem's reference website https://cosmicpvp.com might be subject to copyright. Here are used for illustrative and educative purpose only.
You can find out how wide / tall the screen is:
const width = window.innerWidth;
const height = window.innerHeight;
So you can find the centre of the screen:
const windowCenterX = width / 2;
const windowCenterY = height / 2;
And transform your mouse coordinates appropriately:
const transformedX = x - windowCenterX;
const transformedY = y - windowCenterY;
Small demo:
const coords = document.querySelector("#coords");
document.querySelector("#area").addEventListener("mousemove", (event)=>{
const x = event.clientX;
const y = event.clientY;
const width = window.innerWidth;
const height = window.innerHeight;
const windowCenterX = width / 2;
const windowCenterY = height / 2;
const transformedX = x - windowCenterX;
const transformedY = y - windowCenterY;
coords.textContent = `x: ${transformedX}, y: ${transformedY}`;
});
body, html, #area {
margin: 0;
width: 100%;
height: 100%;
}
#area {
background-color: #eee;
}
#coords {
position: absolute;
left: 10px;
top: 10px;
}
<div id="area"></div>
<div id="coords"></div>
I think I would use the bounding rect of the image to determine the center based on the image itself rather than the screen... something like this (using CSSVars to handle the transform)
const img = document.getElementById('fakeimg')
addEventListener('pointermove', handler)
function handler(e) {
const rect = img.getBoundingClientRect()
const x1 = (rect.x + rect.width / 2)
const y1 = (rect.y + rect.height / 2)
const x2 = e.clientX
const y2 = e.clientY
let angle = Math.atan2(y2 - y1, x2 - x1) * (180 / Math.PI) + 90
angle = angle < 0 ?
360 + angle :
angle
img.style.setProperty('--rotate', angle);
}
*,
*::before,
*::after {
box-sizeing: border-box;
}
html,
body {
height: 100%;
margin: 0
}
body {
display: grid;
place-items: center;
}
[id=fakeimg] {
width: 80vmin;
background: red;
aspect-ratio: 16 / 9;
--rotation: calc(var(--rotate) * 1deg);
transform: rotate(var(--rotation));
}
<div id="fakeimg"></div>
I couldn't find an example where this image sequence animation is triggered at any other part except on the top of the website.
The animation is working fine as it is, the problem is: if i put any content above the canvas, the image sequence will still be linked to the top of the page. So when the user gets to the animation, they will see it already half way through.
I'd like to ask for your help to change the starting point of this animation to only when it enters the viewport, not necessarily on the top of the page.
I'll also leave a link to the same exact snippet running on codepen, just in case: https://codepen.io/querubim_reginaldo/pen/OJzoOXK
Thanks in advance!
const html = document.documentElement;
const canvas = document.getElementById("pro_display_animation");
const context = canvas.getContext("2d");
const currentFrame = index => (
`https://www.apple.com/105/media/us/airpods-pro/2019/1299e2f5_9206_4470_b28e_08307a42f19b/anim/sequence/large/01-hero-lightpass/${(index + 1).toString().padStart(4, '0')}.jpg`
);
// total of image files
const frameCount = 160;
// sets the canvas size to full screen
canvas.width = document.documentElement.clientWidth * 1;
canvas.height = document.documentElement.clientHeight * 1;
// draws the first image on screen
const img = new Image();
img.src = currentFrame(1);
img.onload = function() {
context.drawImage(img, 0, 0);
};
// updates the image in sequence
const updateImage = index => {
img.src = currentFrame(index);
context.drawImage(img, 0, 0);
}
// links the scroll position with the correct image
window.addEventListener('scroll', () => {
const scrollTop = html.scrollTop;
const maxScrollTop = html.scrollHeight - window.innerHeight;
const scrollFraction = scrollTop / maxScrollTop;
const frameIndex = Math.min(
frameCount - 1,
Math.ceil(scrollFraction * frameCount)
);
requestAnimationFrame(() => updateImage(frameIndex + 1))
});
body {
margin: 0;
padding: 0;
}
#section_canvas_animation {
display: flex;
justify-content: center;
align-items: flex-start;
height: 1200vh;
width: 100vw;
background: #efefef;
}
canvas {
position: sticky;
top: 0;
max-width: 100vw;
max-height: 100vh;
}
.website_content {
height: 400vh;
background: darkcyan;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 100px;
}
<body>
<div id="section_canvas_animation">
<canvas id="pro_display_animation"></canvas>
</div>
<div class="website_content">THIS IS SOME WEBSITE CONTENT</div>
</body>
`
Check my demo here: https://www.sfertons.dev/lab/apple-scroll-sequence-animation
The trick is to take the "animation sequence" offsetTop into account and use its scrollHeight to calculate the maxScrollTop:
const scrollTop = document.documentElement.scrollTop - heroSequence.offsetTop
const maxScrollTop = heroSequence.scrollHeight - window.innerHeight
The project is also on my Github: https://github.com/Spharian/apple-sequence-animation
What I'm trying to do (which I've been able to accomplish, but with poor performances) is to apply a sort of grid over the canvas, in order to be able to take inputs from the user about the origin point position. Once the input is received, the "draw" coordinates are provided via keyboard.
What I've managed to do in these days, was to calculate the width and height of the canvas, then divide it by the area of a standard 20x20 square (speaking in px). In this way I can loop on the result and create n squares, that I will render in display flex inside the grid element. Then this grid element is applied "over" the canvas.
Everything works, but there's a lot of divs going around, and if the user choses to shrink the div to let's say 10x10, then, that would have a great impact over the performances... So I'm trying to find out a lighter way to do this...
I've thought about using HR elements inside two divs that would be applied over the canvas. One div displays elements in column, and another in row. In this way I should obtain the grid, but what about the snap? How could I detect the intersection on the two HR elements and use that exact spot as position?
The reason of why I cannot directly draw the grid on the canvas is because this should remain as 'pure' as possible. Containing only the final draw of the user.
Here's the 'non optimized' code:
I'm using Angular 5 as framework.
<div class="draw-zone" #drawZone>
<div class="grid" #grid [ngClass]="{'activated': activateDrawZones}">
<div *ngFor="let block of gridBlocks" class="grid-block" [ngClass]="{'show': showGrid, 'ten-x-ten': blockSize === 10, 'twe-x-twe': blockSize === 20, 'thr-x-thr': blockSize === 30, 'fou-x-fou': blockSize === 40}"
#gridBlock (click)="draw($event, gridBlock)"></div>
</div>
<canvas #canvas [height]="canvasSize.y" [width]="canvasSize.x"></canvas>
</div>
The scss:
.draw-zone{
flex-grow: 2;
height: 100%;
position: relative;
canvas{
z-index: 10;
}
.grid{
top: 0;
left: 0;
z-index: 11;
width: 100%;
display: flex;
flex-wrap: wrap;
overflow: hidden;
position: absolute;
margin-left: -.1rem;
border-radius: .5rem;
align-content: stretch;
border: 1px solid transparent;
&.activated{
border-color: #3f51b5;
}
.grid-block{
opacity: 0;
border-right: 1px solid #3f51b5;
border-bottom: 1px solid #3f51b5;
&.show{
opacity: .1;
}
&:hover{
opacity: 1;
border-radius: 50%;
background-color: #3f51b5;
transform: scale(1.2);
}
&.ten-x-ten{
width: 10px;
height: 10px;
}
&.twe-x-twe{
width: 20px;
height: 20px;
}
&.thr-x-thr{
width: 30px;
height: 30px;
}
&.fou-x-fou{
width: 40px;
height: 40px;
}
}
}
And the component method to cal:
private calculateGrid() {
this.activateDrawZones = false;
this.canvasSize.x = this._drawZone.nativeElement.clientWidth;
this.canvasSize.y = this._drawZone.nativeElement.clientHeight;
const blocksCount = (this.canvasSize.x * this.canvasSize.y) / (this.blockSize * this.blockSize);
this.gridBlocks = [];
for (let i = 0; i < blocksCount; i++) {
this.gridBlocks.push({ size: this.blockSize });
}
this.activateDrawZones = true;
}
And the method that actually draws:
public draw(e: MouseEvent, block: HTMLDivElement, returnOnFail?: boolean) {
const x = block.offsetLeft + (this.blockSize / 2);
const y = block.offsetTop + (this.blockSize / 2);
if (this.firstClick) {
this.ctx.beginPath();
this.ctx.moveTo(x, y);
this.setCrosshair(x, y);
this.firstClick = false;
this.addPathToDrawSequence(x, y);
return;
}
if (this.isNotOnTheSameAxisAsTheLastInsert(x, y)) {
if (returnOnFail) { return; }
this.toggleDrawDirection();
this.draw(e, block, true);
return;
}
this.ctx.lineTo(x, y);
this.ctx.stroke();
this.setCrosshair(x, y);
this.addPathToDrawSequence(x, y);
}
As you can see, I'm applying the '.grid' element over the canvas element. The grid element contains all the blocks that are displayed in flex mode. As you can see the grid container has a display:flex and flex-wrap: wrap properties. In this way, when the user clicks over a block, I can guess the x, y coordinates by getting its position, relative to the parent. Which has the same dimensions as the canvas. Once that I have the x,y coords, i can draw on the canvas.
Yes, creating a multiplicity of DOM elements and trying to dynamically position and size them with javascript will not be particularly performant. I don't think hr elements will solve this problem for you.
First, have you considered drawing your grid directly onto the canvas?
Another option is to have a background image with the grid on it layered behind the canvas. This will automatically resize just as performantly as any other aspect of your webpage.
Now for the 'snapping' part. It looks like you've already figured out how to draw what you need on the canvas once you get the grid information you're looking for. What you need is a method to get which grid a user clicked on. I'm guessing that is why you overlaid all those divs...
Instead, canvas natively tracks mouse clicks. Using some techniques laid out here should be able to get you the grid interaction information you're looking for.
Edit: A method to generate and find grids:
var height = 100;
var width = 200;
var horizontal_grids = 8;
var vertical_grids = 4;
function bounding_grid_1d(length, grids, x) {
var divisions = [];
var grid_width = length / grids;
for ( i = 0; i <= grids; i++ ) {
if (x || x == 0) {
if (i*grid_width > x) {
divisions.push((i-1)*grid_width);
divisions.push(i*grid_width);
break;
}
else if (i*grid_width == x) {
divisions.push(i*grid_width);
break;
}
}
else {
divisions.push(i*grid_width);
}
}
return divisions;
}
console.log("Get all the x and y grid line locations");
console.log(bounding_grid_1d(width, horizontal_grids));
console.log(bounding_grid_1d(height, vertical_grids));
console.log("Get the x and y grid line locations that surround the coordinates (60,30)");
console.log(bounding_grid_1d(width, horizontal_grids, 60));
console.log(bounding_grid_1d(height, vertical_grids, 30));
I am looking to select items in a web page using the LeapMotion and I am struggling with programming this interaction.
Using this code as a base (but updating the link so it connects to the current SDK) I can get my cursor to move around the window based on where my hand is in space. However, I do not know how to make the equivalent of the event listener "click" using the LeapMotion. I am using Leap.js and have built a crude GUI using svg.js.
How do I program an event listener that selects using the LeapMotion in Javascript?
I have working code with Leap motion. I did quize software. Here cursor is a div element which styled as:
#cursor {
width: 60px;
height: 60px;
position: fixed;
margin-left: 20px;
margin-top: 20px;
z-index: 99999;
opacity: 0.9;
background: black;
border-radius: 100%;
background: -webkit-radial-gradient(100px 100px, circle, #f00, #ff6a00);
background: -moz-radial-gradient(100px 100px, circle, #f00, #ff6a00);
background: radial-gradient(100px 100px, circle, #f00, #ff6a00);
}
and Java script at bottom. What I did, I get HTML element by position and triggering click event on it by tap gestures.
var w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
var h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
// Setting cursor and output position
window.cursor = $('#cursor');
var controller= Leap.loop(function(frame) {
if (frame.pointables.length > 0) {
try
{
var position = frame.pointables[0].stabilizedTipPosition;
var normalized = frame.interactionBox.normalizePoint(position);
var cx = w * normalized[0];
var cy = h * (1 - normalized[1]);
$('#cursor').css('left', cx);
$('#cursor').css('top', cy);
}catch(e){
console.error(e);
}
}
});
controller.use('screenPosition', {
scale: 1
});
controller.on('gesture', onGesture);
function onGesture(gesture,frame)
{
try
{
// If gesture type is keyTap
switch(gesture.type)
{
case 'keyTap':
case 'screenTap':
var position = frame.pointables[0].stabilizedTipPosition;
var normalized = frame.interactionBox.normalizePoint(position);
//Hiding cursor for getting background element
cursor.hide();
// Trying find element by position
var cx = w * normalized[0];
var cy = h * (1 - normalized[1]);
var el = document.elementFromPoint(cx, cy);
cursor.show();
console.log(el);
if (el) {
$(el).trigger("click");
}
break;
}
}
catch (e) {
console.info(e);
}
}
I have to show progress graphs exactly in following way where percentage would be in center of circular graph
How can i do this using javascript/jQuery?
Can it be done using Google Chart?
There's a plugin for this at:
http://anthonyterrien.com/knob/
Demo
jQuery Knob
canvas based ; no png or jpg sprites.
touch, mouse and mousewheel, keyboard events implemented.
downward compatible ; overloads an input element...
I searched and know there are at least 5 ways to create Circular progress indicator:
They include:
jquery.polartimer.js
jQuery Knob
CSS3 pie graph timer with jquery
circular progressBar by jQuery andCSS3
ProgressBar.js
I would recommend Highcharts JS for all of your JavaScript graphing needs
Browse through more of the demos; I think you're looking for the Donut Chart :)
You can use CSS sprites (google) for this purpose, if you want to show multiples of 10 (0%, 10%, 20% etc). I guess you need to be a graphics guru to create the sprite..
The sprite is one image containing more than one image. For your purpose, you can create an image, say 16x160px. Imagine that this image is divided into ten 16x16px "slices" and draw the corresponding percentage on that slice. You can then use CSS and JavaScript to show one "frame" from this sprite.
If you are not targeting old browsers, you can easily do that by drawing on a canvas element. This gives you the freedom to do whatever you need with the chart.
That means this solution's only requirement is jQuery and any browser that supports the canvas element...IE9+
Here's a code snippet that demonstrates it...
//input
var dimens = 256;
var color = "rgba(54, 162, 235, 0.9)";
var padding = 12;
var width = 10;
var value = 80;
var maxValue = 100;
var countFontRatio = 0.25; //ratio in relation to the dimens value
$(function() {
$(".chart-total").each(function(idx, element) {
var _render = function() {
var startingPoint = -0.5;
var pointValue = startingPoint;
var currentPoint = startingPoint;
var timer;
var _ctx;
var $canvas = $(element).find("canvas");
var canvas = $canvas.get(0);
pointValue = (value / (maxValue / 20) * 0.1) - 0.5;
canvas.height = dimens;
canvas.width = dimens;
if (!countFontRatio)
$canvas.parent().find(".legend-val").css("font-size", dimens / value.toString().length);
else
$canvas.parent().find(".legend-val").css("font-size", dimens * countFontRatio);
_ctx = canvas.getContext("2d");
var _draw = function() {
_ctx.clearRect(0, 0, dimens, dimens);
_ctx.beginPath();
_ctx.arc(dimens / 2, dimens / 2, (dimens / 2) - padding, startingPoint * Math.PI, 1.5 * Math.PI);
_ctx.strokeStyle = "#ddd";
_ctx.lineWidth = 2;
_ctx.lineCap = "square";
_ctx.stroke();
_ctx.beginPath();
_ctx.arc(dimens / 2, dimens / 2, (dimens / 2) - padding, startingPoint * Math.PI, currentPoint * Math.PI);
_ctx.strokeStyle = color;
_ctx.lineWidth = width;
_ctx.lineCap = "round";
_ctx.stroke();
currentPoint += 0.1;
if (currentPoint > pointValue) {
clearInterval(timer)
}
};
timer = setInterval(_draw, 100);
};
_render();
$(window).resize(function() {
_render();
});
});
})
body {
font-family: 'Open Sans', sans-serif;
color: #757575;
}
.chart-total {
position: relative;
margin: 0 auto;
}
.chart-total-legend {
position: absolute;
top: 50%;
left: 50%;
-ms-transform: translateY(-50%) translateX(-50%);
-o-transform: translateY(-50%) translateX(-50%);
-moz-transform: translateY(-50%) translateX(-50%);
-moz-transform: translateY(-50%) translateX(-50%);
transform: translateY(-50%) translateX(-50%);
}
.legend-val {
font-size: 4em;
display: block;
text-align: center;
font-weight: 300;
font-family: 'Roboto', sans-serif;
}
.legend-desc {
display: block;
margin-top: 5px;
text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<link href="https://fonts.googleapis.com/css?family=Open+Sans|Roboto:300,400" rel="stylesheet">
<div class="chart-total" style="max-width: 256px;">
<canvas height="256" width="256"></canvas>
<div class="chart-total-legend">
<span class="legend-val" value="3933" style="font-size: 64px;">3,933</span>
<span class="legend-desc">Total Progress</span></div>
</div>
I don't think you can do it with javascript/jquery/css alone. You need to render different images, for each step one and display the proper one.
It could be made with flash (probably there are ready made components) or with svg or html5 canvas element or an api which uses one of the above backends.