How to make marker mark any where on progress dynamically - javascript

I have a problem with marker, I want the marker to be stretchable to mark anywhere on the progress bar
As shown in below GIF
Question: I want to select any point on the progress bar and be able to stretch the marker, which can be multiple marker points.
I don't know how to do it with below code:
var player = videojs('demo');
player.markers({
markerStyle: {
'width':'9px',
'border-radius': '40%',
'background-color': 'orange'
},
markerTip:{
display: true,
text: function(marker) {
return "I am a marker tip: "+ marker.text;
}
},
breakOverlay:{
display: true,
displayTime: 4,
style:{
'width':'100%',
'height': '30%',
'background-color': 'rgba(10,10,10,0.6)',
'color': 'white',
'font-size': '16px'
},
text: function(marker) {
return "This is a break overlay: " + marker.overlayText;
},
},
markers: [
{time: 9.5, text: "this", overlayText: "1", class: "special-blue"},
{time: 16, text: "is", overlayText: "2"},
{time: 23.6,text: "so", overlayText: "3"},
{time: 28, text: "cool", overlayText: "4"}
]
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="http://vjs.zencdn.net/4.2/video.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/videojs-markers/0.7.0/videojs-markers.js"></script>
<link href="http://vjs.zencdn.net/4.2/video-js.css" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/videojs-markers/0.7.0/videojs.markers.min.css" rel="stylesheet"/>
<video id="demo" width="400" height="210" controls class="video-js vjs-default-skin">
<source src="http://vjs.zencdn.net/v/oceans.mp4" type="video/mp4">
<source src="http://vjs.zencdn.net/v/oceans.webm" type="video/webm">
</video>

where you want the pointer, just put time in the time: 20.5 and increase the width of the markerStyle: { 'width': '190px' }, so you'll get long line in video progressbar!
here we go
var player = videojs('demo');
player.markers({
markerStyle: {
'width':'190px',
'border-radius': '2px',
'background-color': 'orange'
},
markerTip:{
display: true,
text: function(marker) {
return "I am a marker tip: "+ marker.text;
}
},
breakOverlay:{
display: true,
displayTime: 120,
style:{
'width':'100%',
'height': '30%',
'background-color': 'rgba(10,10,10,0.6)',
'color': 'white',
'font-size': '16px'
},
text: function(marker) {
return "This is a break overlay: " + marker.overlayText;
},
},
markers: [
{time: 20.5, text: "this", overlayText: "1", class: "special-blue"},
]
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="http://vjs.zencdn.net/4.2/video.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/videojs-markers/0.7.0/videojs-markers.js"></script>
<link href="http://vjs.zencdn.net/4.2/video-js.css" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/videojs-markers/0.7.0/videojs.markers.min.css" rel="stylesheet"/>
<video id="demo" width="400" height="210" controls class="video-js vjs-default-skin">
<source src="http://vjs.zencdn.net/v/oceans.mp4" type="video/mp4">
<source src="http://vjs.zencdn.net/v/oceans.webm" type="video/webm">
</video>
you can study here more about all things docs.
if you have any issue please, inform us!
Happy codin'!

One way you can do it is hook into mousedown and mousemove events on seekbar. Add the marker on mousedown with a custom class. Then on mousemove calculate the movement and add the width to the marker element using the custom class.
See this example:
var player = videojs('demo');
// Set variable so we can add values later
let lastAddedMarker = null;
let moving = false;
let seekBar = player.controlBar.progressControl.seekBar;
let startPoint = 0;
// When seekbar is clicked add marker and set values to startpoint and set moving flag to true
seekBar.on('mousedown', function(event) {
var seekBarEl = this.el();
startPoint = videojs.dom.getPointerPosition(seekBarEl, event).x;
player.markers.add([{
time: player.currentTime(),
text: "I'm new",
overlayText: "I'm new",
class: 'custom-marker'
}]);
lastAddedMarker = jQuery(".custom-marker");
moving = true;
});
// When user moves while on seekbar get the width and set it to 'custom-marker' class
seekBar.on("mousemove", function(e) {
if (moving) {
let seekBarEl = this.el();
let movingPoint = videojs.dom.getPointerPosition(seekBarEl, event).x;
let containerWidth = jQuery(seekBarEl).width();
let markerWidth = containerWidth * (movingPoint - startPoint);
lastAddedMarker.width(markerWidth + "px");
}
});
jQuery(document).on("mouseup", function() {
moving = false;
});
player.markers({
markerStyle: {
'width': '9px',
'border-radius': '2px',
'background-color': 'orange'
},
markerTip: {
display: true,
text: function(marker) {
return "I am a marker tip: " + marker.text;
}
},
onMarkerClick: function(marker) {
console.log("AS");
},
breakOverlay: {
display: true,
displayTime: 4,
style: {
'width': '100%',
'height': '30%',
'background-color': 'rgba(10,10,10,0.6)',
'color': 'white',
'font-size': '16px'
},
text: function(marker) {
return "This is a break overlay: " + marker.overlayText;
},
},
markers: []
});
.vjs-marker {
position: absolute;
left: 0;
bottom: 0;
opacity: 1;
height: 100%;
transition: opacity .2s ease;
-webkit-transition: opacity .2s ease;
-moz-transition: opacity .2s ease;
z-index: 100
}
.vjs-break-overlay,
.vjs-tip {
visibility: hidden;
position: absolute;
z-index: 100000
}
.vjs-marker:hover {
cursor: pointer;
-webkit-transform: scale(1.3, 1.3);
-moz-transform: scale(1.3, 1.3);
-o-transform: scale(1.3, 1.3);
-ms-transform: scale(1.3, 1.3);
transform: scale(1.3, 1.3)
}
.vjs-tip {
display: block;
opacity: .8;
padding: 5px;
font-size: 10px;
bottom: 14px
}
.vjs-tip .vjs-tip-arrow {
background: url(data:image/gif;base64,R0lGODlhCQAJAIABAAAAAAAAACH5BAEAAAEALAAAAAAJAAkAAAIRjAOnwIrcDJxvwkplPtchVQAAOw==) bottom left no-repeat;
bottom: 0;
left: 50%;
margin-left: -4px;
position: absolute;
width: 9px;
height: 5px
}
.vjs-tip .vjs-tip-inner {
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
padding: 5px 8px 4px;
background-color: #000;
color: #fff;
max-width: 200px;
text-align: center
}
.vjs-break-overlay {
top: 0
}
.vjs-break-overlay .vjs-break-overlay-text {
padding: 9px;
text-align: center
}
<link href="https://vjs.zencdn.net/7.5.5/video-js.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/video.js/7.6.5/video.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/videojs-markers/0.7.0/videojs.markers.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/videojs-markers/0.7.0/videojs-markers.min.js"></script>
<video id="demo" width="400" height="210" controls class="video-js vjs-default-skin">
<source src="http://vjs.zencdn.net/v/oceans.mp4" type="video/mp4">
<source src="http://vjs.zencdn.net/v/oceans.webm" type="video/webm">
</video>

Related

How to remove face mesh from holistic mediapipe to get better performance

I'm working on holistic mediapipe model (javascript API), it utilizes the pose, face and hand landmark models in MediaPipe Pose, MediaPipe Face Mesh and MediaPipe Hands respectively to generate a total of 543 landmarks (33 pose landmarks, 468 face landmarks, and 21 hand landmarks per hand).
link: https://google.github.io/mediapipe/solutions/holistic#javascript-solution-api
it looks so heavy on devices with low capacity(CPU and RAM), for that i want to remove face mesh that i don't need in my work.
how can i do that with the javascript API ?
import DeviceDetector from "https://cdn.skypack.dev/device-detector-js#2.2.10";
// Usage: testSupport({client?: string, os?: string}[])
// Client and os are regular expressions.
// See: https://cdn.jsdelivr.net/npm/device-detector-js#2.2.10/README.md for
// legal values for client and os
testSupport([
{client: 'Chrome'},
]);
function testSupport(supportedDevices:{client?: string; os?: string;}[]) {
const deviceDetector = new DeviceDetector();
const detectedDevice = deviceDetector.parse(navigator.userAgent);
let isSupported = false;
for (const device of supportedDevices) {
if (device.client !== undefined) {
const re = new RegExp(`^${device.client}$`);
if (!re.test(detectedDevice.client.name)) {
continue;
}
}
if (device.os !== undefined) {
const re = new RegExp(`^${device.os}$`);
if (!re.test(detectedDevice.os.name)) {
continue;
}
}
isSupported = true;
break;
}
if (!isSupported) {
alert(`This demo, running on ${detectedDevice.client.name}/${detectedDevice.os.name}, ` +
`is not well supported at this time, continue at your own risk.`);
}
}
const controls = window;
const mpHolistic = window;
const drawingUtils = window;
const config = {locateFile: (file) => {
return `https://cdn.jsdelivr.net/npm/#mediapipe/holistic#` +
`${mpHolistic.VERSION}/${file}`;
}};
// Our input frames will come from here.
const videoElement =
document.getElementsByClassName('input_video')[0] as HTMLVideoElement;
const canvasElement =
document.getElementsByClassName('output_canvas')[0] as HTMLCanvasElement;
const controlsElement =
document.getElementsByClassName('control-panel')[0] as HTMLDivElement;
const canvasCtx = canvasElement.getContext('2d')!;
// We'll add this to our control panel later, but we'll save it here so we can
// call tick() each time the graph runs.
const fpsControl = new controls.FPS();
// Optimization: Turn off animated spinner after its hiding animation is done.
const spinner = document.querySelector('.loading')! as HTMLDivElement;
spinner.ontransitionend = () => {
spinner.style.display = 'none';
};
function removeElements(
landmarks: mpHolistic.NormalizedLandmarkList, elements: number[]) {
for (const element of elements) {
delete landmarks[element];
}
}
function removeLandmarks(results: mpHolistic.Results) {
if (results.poseLandmarks) {
removeElements(
results.poseLandmarks,
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16, 17, 18, 19, 20, 21, 22]);
}
}
function connect(
ctx: CanvasRenderingContext2D,
connectors:
Array<[mpHolistic.NormalizedLandmark, mpHolistic.NormalizedLandmark]>):
void {
const canvas = ctx.canvas;
for (const connector of connectors) {
const from = connector[0];
const to = connector[1];
if (from && to) {
if (from.visibility && to.visibility &&
(from.visibility < 0.1 || to.visibility < 0.1)) {
continue;
}
ctx.beginPath();
ctx.moveTo(from.x * canvas.width, from.y * canvas.height);
ctx.lineTo(to.x * canvas.width, to.y * canvas.height);
ctx.stroke();
}
}
}
let activeEffect = 'mask';
function onResults(results: mpHolistic.Results): void {
// Hide the spinner.
document.body.classList.add('loaded');
// Remove landmarks we don't want to draw.
removeLandmarks(results);
// Update the frame rate.
fpsControl.tick();
// Draw the overlays.
canvasCtx.save();
canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
if (results.segmentationMask) {
canvasCtx.drawImage(
results.segmentationMask, 0, 0, canvasElement.width,
canvasElement.height);
// Only overwrite existing pixels.
if (activeEffect === 'mask' || activeEffect === 'both') {
canvasCtx.globalCompositeOperation = 'source-in';
// This can be a color or a texture or whatever...
canvasCtx.fillStyle = '#00FF007F';
canvasCtx.fillRect(0, 0, canvasElement.width, canvasElement.height);
} else {
canvasCtx.globalCompositeOperation = 'source-out';
canvasCtx.fillStyle = '#0000FF7F';
canvasCtx.fillRect(0, 0, canvasElement.width, canvasElement.height);
}
// Only overwrite missing pixels.
canvasCtx.globalCompositeOperation = 'destination-atop';
canvasCtx.drawImage(
results.image, 0, 0, canvasElement.width, canvasElement.height);
canvasCtx.globalCompositeOperation = 'source-over';
} else {
canvasCtx.drawImage(
results.image, 0, 0, canvasElement.width, canvasElement.height);
}
// Connect elbows to hands. Do this first so that the other graphics will draw
// on top of these marks.
canvasCtx.lineWidth = 5;
if (results.poseLandmarks) {
if (results.rightHandLandmarks) {
canvasCtx.strokeStyle = 'white';
connect(canvasCtx, [[
results.poseLandmarks[mpHolistic.POSE_LANDMARKS.RIGHT_ELBOW],
results.rightHandLandmarks[0]
]]);
}
if (results.leftHandLandmarks) {
canvasCtx.strokeStyle = 'white';
connect(canvasCtx, [[
results.poseLandmarks[mpHolistic.POSE_LANDMARKS.LEFT_ELBOW],
results.leftHandLandmarks[0]
]]);
}
}
// Pose...
drawingUtils.drawConnectors(
canvasCtx, results.poseLandmarks, mpHolistic.POSE_CONNECTIONS,
{color: 'white'});
drawingUtils.drawLandmarks(
canvasCtx,
Object.values(mpHolistic.POSE_LANDMARKS_LEFT)
.map(index => results.poseLandmarks[index]),
{visibilityMin: 0.65, color: 'white', fillColor: 'rgb(255,138,0)'});
drawingUtils.drawLandmarks(
canvasCtx,
Object.values(mpHolistic.POSE_LANDMARKS_RIGHT)
.map(index => results.poseLandmarks[index]),
{visibilityMin: 0.65, color: 'white', fillColor: 'rgb(0,217,231)'});
// Hands...
drawingUtils.drawConnectors(
canvasCtx, results.rightHandLandmarks, mpHolistic.HAND_CONNECTIONS,
{color: 'white'});
drawingUtils.drawLandmarks(canvasCtx, results.rightHandLandmarks, {
color: 'white',
fillColor: 'rgb(0,217,231)',
lineWidth: 2,
radius: (data: drawingUtils.Data) => {
return drawingUtils.lerp(data.from!.z!, -0.15, .1, 10, 1);
}
});
drawingUtils.drawConnectors(
canvasCtx, results.leftHandLandmarks, mpHolistic.HAND_CONNECTIONS,
{color: 'white'});
drawingUtils.drawLandmarks(canvasCtx, results.leftHandLandmarks, {
color: 'white',
fillColor: 'rgb(255,138,0)',
lineWidth: 2,
radius: (data: drawingUtils.Data) => {
return drawingUtils.lerp(data.from!.z!, -0.15, .1, 10, 1);
}
});
// Face...
drawingUtils.drawConnectors(
canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_TESSELATION,
{color: '#C0C0C070', lineWidth: 1});
drawingUtils.drawConnectors(
canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_RIGHT_EYE,
{color: 'rgb(0,217,231)'});
drawingUtils.drawConnectors(
canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_RIGHT_EYEBROW,
{color: 'rgb(0,217,231)'});
drawingUtils.drawConnectors(
canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_LEFT_EYE,
{color: 'rgb(255,138,0)'});
drawingUtils.drawConnectors(
canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_LEFT_EYEBROW,
{color: 'rgb(255,138,0)'});
drawingUtils.drawConnectors(
canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_FACE_OVAL,
{color: '#E0E0E0', lineWidth: 5});
drawingUtils.drawConnectors(
canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_LIPS,
{color: '#E0E0E0', lineWidth: 5});
canvasCtx.restore();
}
const holistic = new mpHolistic.Holistic(config);
holistic.onResults(onResults);
// Present a control panel through which the user can manipulate the solution
// options.
new controls
.ControlPanel(controlsElement, {
selfieMode: true,
modelComplexity: 1,
smoothLandmarks: true,
enableSegmentation: false,
smoothSegmentation: true,
minDetectionConfidence: 0.5,
minTrackingConfidence: 0.5,
effect: 'background',
})
.add([
new controls.StaticText({title: 'MediaPipe Holistic'}),
fpsControl,
new controls.Toggle({title: 'Selfie Mode', field: 'selfieMode'}),
new controls.SourcePicker({
onSourceChanged: () => {
// Resets because the pose gives better results when reset between
// source changes.
holistic.reset();
},
onFrame:
async (input: controls.InputImage, size: controls.Rectangle) => {
const aspect = size.height / size.width;
let width: number, height: number;
if (window.innerWidth > window.innerHeight) {
height = window.innerHeight;
width = height / aspect;
} else {
width = window.innerWidth;
height = width * aspect;
}
canvasElement.width = width;
canvasElement.height = height;
await holistic.send({image: input});
},
}),
new controls.Slider({
title: 'Model Complexity',
field: 'modelComplexity',
discrete: ['Lite', 'Full', 'Heavy'],
}),
new controls.Toggle(
{title: 'Smooth Landmarks', field: 'smoothLandmarks'}),
new controls.Toggle(
{title: 'Enable Segmentation', field: 'enableSegmentation'}),
new controls.Toggle(
{title: 'Smooth Segmentation', field: 'smoothSegmentation'}),
new controls.Slider({
title: 'Min Detection Confidence',
field: 'minDetectionConfidence',
range: [0, 1],
step: 0.01
}),
new controls.Slider({
title: 'Min Tracking Confidence',
field: 'minTrackingConfidence',
range: [0, 1],
step: 0.01
}),
new controls.Slider({
title: 'Effect',
field: 'effect',
discrete: {'background': 'Background', 'mask': 'Foreground'},
}),
])
.on(x => {
const options = x as mpHolistic.Options;
videoElement.classList.toggle('selfie', options.selfieMode);
activeEffect = (x as {[key: string]: string})['effect'];
holistic.setOptions(options);
});
#keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.abs {
position: absolute;
}
a {
color: white;
text-decoration: none;
&:hover {
color: lightblue;
}
}
body {
bottom: 0;
font-family: 'Titillium Web', sans-serif;
color: white;
left: 0;
margin: 0;
position: absolute;
right: 0;
top: 0;
transform-origin: 0px 0px;
overflow: hidden;
}
.container {
position: absolute;
background-color: #596e73;
width: 100%;
max-height: 100%;
}
.input_video {
display: none;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
&.selfie {
transform: scale(-1, 1);
}
}
.input_image {
position: absolute;
}
.canvas-container {
display:flex;
height: 100%;
width: 100%;
justify-content: center;
align-items:center;
}
.output_canvas {
max-width: 100%;
display: block;
position: relative;
left: 0;
top: 0;
}
.logo {
bottom: 10px;
right: 20px;
.title {
color: white;
font-size: 28px;
}
.subtitle {
position: relative;
color: white;
font-size: 10px;
left: -30px;
top: 20px;
}
}
.control-panel {
position: absolute;
left: 10px;
top: 10px;
}
.loading {
display: flex;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
align-items: center;
backface-visibility: hidden;
justify-content: center;
opacity: 1;
transition: opacity 1s;
.message {
font-size: x-large;
}
.spinner {
position: absolute;
width: 120px;
height: 120px;
animation: spin 1s linear infinite;
border: 32px solid #bebebe;
border-top: 32px solid #3498db;
border-radius: 50%;
}
}
.loaded .loading {
opacity: 0;
}
.shoutout {
left: 0;
right: 0;
bottom: 40px;
text-align: center;
font-size: 24px;
position: absolute;
}
<head> <meta charset="utf-8">
<link rel="icon" href="favicon.ico">
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/#mediapipe/control_utils#0.6/control_utils.css" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="demo.css" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/#mediapipe/camera_utils#0.3/camera_utils.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/#mediapipe/control_utils#0.6/control_utils.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/#mediapipe/drawing_utils#0.3/drawing_utils.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/#mediapipe/holistic#0.5/holistic.js" crossorigin="anonymous"></script></head>
<div class="container">
<video class="input_video"></video>
<div class="canvas-container">
<canvas class="output_canvas" width="1280px" height="720px">
</canvas>
</div>
<div class="loading">
<div class="spinner"></div>
<div class="message">
Loading
</div>
</div>
<a class="abs logo" href="http://www.mediapipe.dev" target="_blank">
<div style="display: flex;align-items: center;bottom: 0;right: 10px;">
<img class="logo" src="logo_white.png" alt="" style="
height: 50px;">
<span class="title">MediaPipe</span>
</div>
</a>
<div class="shoutout">
<div>
<a href="https://solutions.mediapipe.dev/holistic">
Click here for more info
</a>
</div>
</div>
</div>
<div class="control-panel">
</div>
.
web code and demo:
Just remove these lines of code:
drawingUtils.drawConnectors(
canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_TESSELATION,
{color: '#C0C0C070', lineWidth: 1});
drawingUtils.drawConnectors(
canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_RIGHT_EYE,
{color: 'rgb(0,217,231)'});
drawingUtils.drawConnectors(
canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_RIGHT_EYEBROW,
{color: 'rgb(0,217,231)'});
drawingUtils.drawConnectors(
canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_LEFT_EYE,
{color: 'rgb(255,138,0)'});
drawingUtils.drawConnectors(
canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_LEFT_EYEBROW,
{color: 'rgb(255,138,0)'});
drawingUtils.drawConnectors(
canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_FACE_OVAL,
{color: '#E0E0E0', lineWidth: 5});
drawingUtils.drawConnectors(
canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_LIPS,
{color: '#E0E0E0', lineWidth: 5});```

swiper carousel + animation + mask image

I am building a Swiper carousel (triggered by mouse scrolling) that has a frame on the top of it. this is the design
the red color is the frame that should cover the carousel. the middle hole is transparent
I have tried to make the red image as a mask-image so that I can control the swiper carousel by mouse scrolling, but the center hole goes white and the red color is transparent! and what I want is exactly the opposite I want the hole transplant and the outside the hole the red color.
Is there any way to add the image frame on the top of the swiper carousel and still can trigger and control the carousel positioned under the frame?
Code:
codePen
// https://swiperjs.com/
// ===================== -->
var mySwiper = new Swiper('.swiper-container', {
// Optional parameters
direction: 'horizontal',
loop: true,
speed: 1200,
grabCursor: true,
mousewheel: true,
// If we need pagination
pagination: {
el: '.swiper-pagination',
type: 'bullets',
clickable: true,
},
// Navigation arrows
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
on: {
slideChangeTransitionStart: function() {
// Slide captions
var swiper = this;
setTimeout(function() {
var currentTitle = $(swiper.slides[swiper.activeIndex]).attr("data-title");
var currentSubtitle = $(swiper.slides[swiper.activeIndex]).attr("data-subtitle");
}, 500);
gsap.to($(".current-title"), 0.4, {
autoAlpha: 0,
y: -40,
ease: Power1.easeIn
});
gsap.to($(".current-subtitle"), 0.4, {
autoAlpha: 0,
y: -40,
delay: 0.15,
ease: Power1.easeIn
});
},
slideChangeTransitionEnd: function() {
// Slide captions
var swiper = this;
var currentTitle = $(swiper.slides[swiper.activeIndex]).attr("data-title");
var currentSubtitle = $(swiper.slides[swiper.activeIndex]).attr("data-subtitle");
$(".slide-captions").html(function() {
return "<h2 class='current-title'>" + currentTitle + "</h2>" + "<h3 class='current-subtitle'>" + currentSubtitle + "</h3>";
});
gsap.from($(".current-title"), 0.4, {
autoAlpha: 0,
y: 40,
ease: Power1.easeOut
});
gsap.from($(".current-subtitle"), 0.4, {
autoAlpha: 0,
y: 40,
delay: 0.15,
ease: Power1.easeOut
});
}
}
});
// Slide captions
var currentTitle = $(mySwiper.slides[mySwiper.activeIndex]).attr("data-title");
var currentSubtitle = $(mySwiper.slides[mySwiper.activeIndex]).attr("data-subtitle");
$(".slide-captions").html(function() {
return "<h2 class='current-title'>" + currentTitle + "</h2>" + "<h3 class='current-subtitle'>" + currentSubtitle + "</h3>";
});
body {
margin: 0;
}
/* Swiper */
.swiper-container {
position: absolute;
width: 100%;
height: 100vh;
mask-image: url(https://i.ibb.co/kmBv430/Frame.png);
mask-size: contain;
}
/* .above{
position:absolute;
left:25%;
background-color: #fff;
width: 200%;
height: 100vh;
z-index:2;
mask-image: radial-gradient(circle at center, transparent 49%, white 50%);
mask-size: contain;
mask-repeat: no-repeat;
} */
/* Swiper slides */
.swiper-slide {
position: relative;
z-index: 1;
}
.slide-1 {
background-color: #e67204;
}
.slide-2 {
background-color: #e67204;
}
.slide-3 {
background-color: #e67204;
}
.rounded-circle {
width: 400px;
height: 300px;
border-radius: 50%;
position: absolute;
top: 30%;
left: 35%
}
.htu {
position: absolute;
color: #fff;
font-size: 50px;
top: 39%;
left: 10%;
z-index: 2;
}
/* Slide captions */
.slide-captions {
position: absolute;
top: 50%;
left: 75%;
color: #FFF;
z-index: 999;
transform: translateY(-50%);
}
.slide-captions .current-title {
position: absolute;
left: 60%;
margin: 0;
font-size: 48px;
}
.slide-captions .current-subtitle {
margin: 10px 0 0 0;
font-size: 28px;
}
/* Swiper arrows */
.swiper-pagination-bullet-active {
background-color: #fff;
}
/* Swiper pagination */
.swiper-container-horizontal>.swiper-pagination-bullets {
bottom: 50px;
}
.swiper-button-prev,
.swiper-button-next {
color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.3.0/gsap.min.js"></script>
<script src="https://unpkg.com/swiper#6.3.2/swiper-bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!-- Slider main container -->
<div class="swiper-container">
<h1 class="htu">HOW TO USE</h1>
<div class="above"></div>
<!-- Additional required wrapper -->
<div class="swiper-wrapper">
<!-- Slides -->
<div class="swiper-slide slide-1" data-title="Slide One" data-subtitle="">
<img width="150" height="150" src="https://i.pravatar.cc/300" class="rounded-circle " alt="">
</div>
<div class="swiper-slide slide-2" data-title="Slide Two" data-subtitle=" ">
<img width="150" height="150" src="https://i.pravatar.cc/300" class="rounded-circle " alt="">
</div>
<div class="swiper-slide slide-3" data-title="Slide Three" data-subtitle=" ">
<img width="150" height="150" src="https://i.pravatar.cc/300" class="rounded-circle " alt="">
</div>
</div>
</div>
<!-- Slide captions -->
<div class="slide-captions"></div>
<!-- If we need pagination -->
<div class="swiper-pagination"></div>
<!-- If we need navigation buttons -->
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
I have solved my problem by using GSAP scroll Trigger to trigger the movement of each layer and it works for me.
let tl2 = gsap.timeline({
scrollTrigger :{
trigger : "#sec-4",
pin: true,
scrub: true,
start : "center center",
end: "+=" + (window.innerHeight * 8),
}
});
tl2.from('.step-1', 1, {y: 100, opacity:0 })
tl2.to('.step-1', 1, {opacity:0 })
tl2.to('.flwres-frame', 0.5 , {x: -300}, 'frist')
tl2.to('.girl-frame', 1 , {x: -300}, 'frist')
tl2.from('.step-2', 1, {y: 100, opacity:0 }, 'frist')
tl2.to('.step-2', 1, {opacity:0 })
tl2.to('.flwres-frame', 0.5 , {x: -600}, 'second')
tl2.to('.girl-frame', 1 , {x: -670}, 'second')
tl2.from('.step-3', 1, {y: 100, opacity:0 }, 'second')
tl2.to('.step-3', 1, {opacity:0 })
tl2.to('.flwres-frame', 0.5 , {x: -900}, '3rd')
tl2.to('.girl-frame', 1 , {x: -1050}, '3rd')
tl2.from('.step-4', 2, {y: 100, opacity:0 }, '3rd')

How to trigger click on canvas object and DOM element that is placed on top of object at the same time?

I have some circles that can be added to a fabricjs canvas. Each circle is an object, while outside my javascript code I have a DOM element, that looks like this:
<span id="cirkel1" class="carttip inlineflexmenu" style="border-radius:100%;width: 25px;height:25px;z-index:9999999;position:absolute;cursor:pointer;">
<div class="tooltipcontent darktext tooltippadding" style="position:relative;">
Testtest
</div>
</span>
This element triggers a tooltip with Tippjs (a js tooltip package), that has the following code (don't mind the each loop, I should also mention below code is outside the canvas function):
$( "#cirkel1" ).each(function( i ) {
tippy(this, {
theme: 'blue',
trigger: 'click',
allowHTML: true,
placement: 'right',
animation: 'scale-subtle',
interactive: true,
content: function (reference) {
return reference.querySelector('.tooltipcontent');
}
});
});
Inside my function where I declare everything for the canvas, I have the following code to place the DOM element on top of the canvas object:
fabric.Canvas.prototype.getAbsoluteCoords = function(object) {
return {
left: object.left + this._offset.left,
top: object.top + this._offset.top
};
}
var cirkel1tooltip = document.getElementById('cirkel1'),
btnWidth = 40,
btnHeight = 40;
function positionBtn(obj) {
var absCoords = canvas.getAbsoluteCoords(obj);
cirkel1tooltip.style.left = (absCoords.left - btnWidth / 10) + 'px';
cirkel1tooltip.style.top = (absCoords.top - btnHeight / 10) + 'px';
}
This works, and the tooltip shows when clicked, but in my canvas function I also have a click function which toggles an image for a specific circle when clicked. I need both to trigger at the same time when the circle is clicked, now when I click a circle, the image appears, but only after I click a second time, the tooltip appears too, not at the same first click.
Removing the image by clicking a second time also doesn't work untill I click on another circle and then click back on the previously clicked circle.
The strange thing is, when I remove one of the two functions (tooltip click, or image toggle click) it works instant, but together only the image toggle works right away but the tooltip only after a second click. Why is that?
The entire code can be seen here (click the small circles to test): https://codepen.io/twan2020/pen/jOVaWMm
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<base href="//printzelf.nl/new/">
<title>Image test</title>
<link rel="stylesheet" href="https://unpkg.com/tippy.js#6/animations/scale-subtle.css"/>
<link rel="stylesheet"href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
<script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous"></script>
</head>
<body>
<style media="screen">
.tippy-box {
width: 100%!important;
text-align: center;
background-color: #fff!important;
color: #fff!important;
box-shadow: 3px 2px 15px 6px rgb(0 0 0 / 10%);
}
.darktext {
color: #383838;
font-family: Panton;
font-size: 15px;
}
.tooltippadding {
padding: 15px;
}
body .tippy-arrow {
color: #fff!important;
}
</style>
<img id="background" src="https://static01.nyt.com/images/2019/05/29/realestate/00skyline-south4/88ce0191bfc249b6aae1b472158cccc4-superJumbo.jpg" alt="" style="display:none;">
<div class="canvas-container" style="width: 600px; height: 500px; position: relative; user-select: none;">
<canvas id="c" width="600" height="500" class="lower-canvas" style="border:1px solid red;position: absolute; width: 600px; height: 500px; left: 0px; top: 0px; touch-action: none; user-select: none;"></canvas>
</div>
</body>
<span id="cirkel1" class="carttip inlineflexmenu" style="border-radius:100%;width: 25px;height:25px;z-index:9999999;position:absolute;cursor:pointer;">
<div class="tooltipcontent darktext tooltippadding" style="position:relative;">
Testtest
</div>
</span>
<!-- Popper JS -->
<script src="assets/js/popper.min.js"></script>
<script src="https://unpkg.com/tippy.js#6"></script>
<script type="text/javascript" src="assets/js/fabric.js"></script>
<script type="text/javascript">
(function() {
var myImg = document.querySelector("#background");
var realWidth = myImg.naturalWidth;
var realHeight = myImg.naturalHeight;
var source = document.getElementById('background').src;
var canvas = new fabric.Canvas('c');
canvas.hoverCursor = 'pointer';
canvas.selection = false;
canvas.setDimensions({ width: realWidth, height: realHeight });
var img = new Image();
// use a load callback to add image to canvas.
img.src = 'https://static01.nyt.com/images/2019/05/29/realestate/00skyline-south4/88ce0191bfc249b6aae1b472158cccc4-superJumbo.jpg';
canvas.setBackgroundImage(source, canvas.renderAll.bind(canvas), {
backgroundImageOpacity: 0.5,
backgroundImageStretch: false
});
const hotspots = [
{
top: 140,
left: 230,
radius: 10,
fill: '#009fe3',
id: 'cirkel1',
hoverCursor: 'pointer',
selectable: false,
imgtop: 200,
imgleft: 300,
imgheight: 200,
imgwidth: 200,
tooltipid: 'cirkel1',
imgUrl: 'https://printzelf.nl/new/assets/images/logo_gewoon.png'
},
{
top: 240,
left: 530,
radius: 10,
fill: '#009fe3',
id: 'cirkel2',
hoverCursor: 'pointer',
selectable: false,
imgtop: 200,
imgleft: 700,
imgheight: 200,
imgwidth: 200,
imgUrl: 'https://i1.wp.com/nypost.com/wp-content/uploads/sites/2/2020/04/pugs-coronavirus.jpg'
},
{
top: 240,
left: 730,
radius: 10,
fill: '#009fe3',
id: 'cirkel2',
hoverCursor: 'pointer',
selectable: false,
imgtop: 200,
imgleft: 800,
imgheight: 200,
imgwidth: 200,
imgUrl: 'https://i.guim.co.uk/img/media/fe1e34da640c5c56ed16f76ce6f994fa9343d09d/0_174_3408_2046/master/3408.jpg?width=1200&height=900&quality=85&auto=format&fit=crop&s=0d3f33fb6aa6e0154b7713a00454c83d'
}
];
const loadedImages = [];
for (let [idx, props] of hotspots.entries()) {
let c = new fabric.Circle(props);
c.class = 'hotspot';
c.name = 'hotspot-' + idx;
canvas.add(c);
}
fabric.Canvas.prototype.getAbsoluteCoords = function(object) {
return {
left: object.left + this._offset.left,
top: object.top + this._offset.top
};
}
var cirkel1tooltip = document.getElementById('cirkel1'),
btnWidth = 40,
btnHeight = 40;
function positionBtn(obj) {
var absCoords = canvas.getAbsoluteCoords(obj);
cirkel1tooltip.style.left = (absCoords.left - btnWidth / 10) + 'px';
cirkel1tooltip.style.top = (absCoords.top - btnHeight / 10) + 'px';
}
for (const ho of canvas.getObjects()) {
// check for 'hotspot' class
if (ho.class && ho.class === 'hotspot') {
ho.on('mousedown', () => {
// check if image was previously loaded
if (loadedImages.indexOf(ho.name) < 0) {
// image is not in the array
// so it needs to be loaded
// prepare the image properties
let imgProps = {
width: ho.imgwidth,
height: ho.imgheight,
left: ho.imgleft,
top: ho.imgtop,
scaleX: .25,
scaleY: .25,
selectable: false,
id: 'img-' + ho.name,
hoverCursor: "default"
};
var printzelfImg = new Image();
printzelfImg.onload = function (img) {
var printzelf = new fabric.Image(printzelfImg, imgProps);
canvas.add(printzelf);
};
printzelfImg.src = ho.imgUrl;
// update the `loadedImages` array
loadedImages.push(ho.name);
} else {
// image was previously loaded
for (const o of canvas.getObjects()) {
// find the correct image on the canvas
if (o.id && o.id === 'img-' + ho.name) {
// toggle the visible property
o.visible = !o.visible;
break;
}
}
}
positionBtn(ho);
});
}
}
})();
$( "#cirkel1" ).each(function( i ) {
tippy(this, {
theme: 'blue',
trigger: 'click',
allowHTML: true,
placement: 'right',
animation: 'scale-subtle',
interactive: true,
content: function (reference) {
return reference.querySelector('.tooltipcontent');
}
});
});
</script>
</html>
Also, is it possible to attach different tooltips to each dot/circle?
The reason why it didn't work is that you have just 1 DIV hotspot and you move this hotspot on mousedown and expect it to trigger the onclick event afterward which doesn't work. The reason why it works on second click is that the hotspot it now there.
The solution is to have the same amount of DIV as you have hotspot. This allows you to have unique popup message. Currently it displays the same message for each hotspot.
There is an onShow(instance), and onHide(instance) for the tippy property which allows you to carryout extra functionality when these hotspot are clicked on. In your case you want to load images related to the selected hotspot. This eliminate having two events setup.
There was also a problem toggling images. I fixed this but I am not 100% certain it is working how you would like this to work.
Also you had HTML tags outside the <body> tag and HTML content aren't supposed to exist outside body.
I kept most of your original code as much as I can.
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<base href="//printzelf.nl/new/">
<title>Image test</title>
<link rel="stylesheet" href="https://unpkg.com/tippy.js#6/animations/scale-subtle.css"/>
<link rel="stylesheet"href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
<script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous"></script>
<style media="screen">
.tippy-box {
width: 100%!important;
text-align: center;
background-color: #fff!important;
color: #fff!important;
box-shadow: 3px 2px 15px 6px rgb(0 0 0 / 10%);
}
.darktext {
color: #383838;
font-family: Panton;
font-size: 15px;
}
.tooltippadding {
padding: 15px;
}
body .tippy-arrow {
color: #fff!important;
}
</style>
</head>
<body>
<img id="background" src="https://static01.nyt.com/images/2019/05/29/realestate/00skyline-south4/88ce0191bfc249b6aae1b472158cccc4-superJumbo.jpg" alt="" style="display:none;">
<div class="canvas-container" style="width: 600px; height: 500px; position: relative; user-select: none;">
<canvas id="c" width="600" height="500" class="lower-canvas" style="border:1px solid red;position: absolute; width: 600px; height: 500px; left: 0px; top: 0px; touch-action: none; user-select: none;"></canvas>
</div>
<span id="cirkel1" class="carttip inlineflexmenu" style="border-radius:100%;width: 25px;height:25px;position:absolute;cursor:pointer;">
<div class="tooltipcontent1 tooltipcontent darktext tooltippadding" style="position:relative;">
Message 1
</div>
</span>
<span id="cirkel2" class="carttip inlineflexmenu" style="border-radius:100%;width: 25px;height:25px;position:absolute;cursor:pointer;">
<div class="tooltipcontent2 tooltipcontent darktext tooltippadding" style="position:relative;">
Message 2
</div>
</span>
<span id="cirkel3" class="carttip inlineflexmenu" style="border-radius:100%;width: 25px;height:25px;position:absolute;cursor:pointer;">
<div class="tooltipcontent3 tooltipcontent darktext tooltippadding" style="position:relative;">
Message 3
</div>
</span>
<!-- Popper JS -->
<script src="assets/js/popper.min.js"></script>
<script src="https://unpkg.com/tippy.js#6"></script>
<script type="text/javascript" src="assets/js/fabric.js"></script>
<script type="text/javascript">
(function() {
var myImg = document.querySelector("#background");
var realWidth = myImg.naturalWidth;
var realHeight = myImg.naturalHeight;
var source = document.getElementById('background').src;
var canvas = new fabric.Canvas('c');
canvas.hoverCursor = 'pointer';
canvas.selection = false;
canvas.setDimensions({ width: realWidth, height: realHeight });
var img = new Image();
// use a load callback to add image to canvas.
img.src = 'https://static01.nyt.com/images/2019/05/29/realestate/00skyline-south4/88ce0191bfc249b6aae1b472158cccc4-superJumbo.jpg';
canvas.setBackgroundImage(source, canvas.renderAll.bind(canvas), {
backgroundImageOpacity: 0.5,
backgroundImageStretch: false
});
const hotspots = [
{
top: 140,
left: 230,
radius: 10,
fill: '#009fe3',
id: 'cirkel1',
hoverCursor: 'pointer',
selectable: false,
imgtop: 200,
imgleft: 300,
imgheight: 200,
imgwidth: 200,
tooltipid: 'cirkel1',
imgUrl: 'https://printzelf.nl/new/assets/images/logo_gewoon.png'
},
{
top: 240,
left: 530,
radius: 10,
fill: '#009fe3',
id: 'cirkel2',
hoverCursor: 'pointer',
selectable: false,
imgtop: 200,
imgleft: 700,
imgheight: 200,
imgwidth: 200,
imgUrl: 'https://i1.wp.com/nypost.com/wp-content/uploads/sites/2/2020/04/pugs-coronavirus.jpg'
},
{
top: 240,
left: 730,
radius: 10,
fill: '#009fe3',
id: 'cirkel2',
hoverCursor: 'pointer',
selectable: false,
imgtop: 200,
imgleft: 800,
imgheight: 200,
imgwidth: 200,
imgUrl: 'https://i.guim.co.uk/img/media/fe1e34da640c5c56ed16f76ce6f994fa9343d09d/0_174_3408_2046/master/3408.jpg?width=1200&height=900&quality=85&auto=format&fit=crop&s=0d3f33fb6aa6e0154b7713a00454c83d'
}
];
const loadedImages = [];
for (let [idx, props] of hotspots.entries()) {
let c = new fabric.Circle(props);
c.class = 'hotspot';
c.name = 'hotspot-' + idx;
canvas.add(c);
}
fabric.Canvas.prototype.getAbsoluteCoords = function(object) {
return {
left: object.left + this._offset.left,
top: object.top + this._offset.top
};
}
var cirkel1tooltip = document.getElementById('cirkel1'),
btnWidth = 40,
btnHeight = 40;
function positionBtn(obj, index) {
var absCoords = canvas.getAbsoluteCoords(obj);
var element = document.getElementById('cirkel'+index);
element.style.left = (absCoords.left - btnWidth / 10) + 'px';
element.style.top = (absCoords.top - btnHeight / 10) + 'px';
}
canvas.getObjects().forEach(function(ho, index) {
positionBtn(ho, index + 1);
});
$( ".carttip" ).each(function( i ) {
tippy(this, {
theme: 'blue',
trigger: 'click',
allowHTML: true,
placement: 'right',
animation: 'scale-subtle',
interactive: true,
onShow(instance) {
canvas.getObjects().forEach(function(ho, index) {
if (ho.class && ho.class === 'hotspot') {
if (instance.id == index + 1) {
// check if image was previously loaded
if (loadedImages.indexOf(ho.name) < 0) {
// image is not in the array
// so it needs to be loaded
// prepare the image properties
let imgProps = {
width: ho.imgwidth,
height: ho.imgheight,
left: ho.imgleft,
top: ho.imgtop,
scaleX: .25,
scaleY: .25,
selectable: false,
id: 'img-' + ho.name,
hoverCursor: "default",
};
var printzelfImg = new Image();
printzelfImg.onload = function (img) {
var printzelf = new fabric.Image(printzelfImg, imgProps);
printzelf.trippyHotspotImage = true;
canvas.add(printzelf);
};
printzelfImg.src = ho.imgUrl;
// update the `loadedImages` array
loadedImages.push(ho.name);
} else {
for (const o of canvas.getObjects()) {
if (o.id && o.id === 'img-' + ho.name) {
o.visible = true;
break;
}
}
canvas.renderAll();
}
}
}
});
},
onHide(instance) {
for (const o of canvas.getObjects()) {
if (o.trippyHotspotImage) {
o.visible = false;
}
}
canvas.renderAll();
},
content: function (reference) {
return reference.querySelector('.tooltipcontent' + (i + 1));
}
});
});
})();
</script>
</body>
</html>
It looks like the first time the click event isn't fired right after the mousedown one the first time. The framework you use seems to prevent this because a process (by the listener) is performed.
(It may be related to event propagation but at this time I still didn't find out how to prevent a click event to be fired after a mouseup.)
What I would call a workaround: to display the tool tip in the same click, i.e. a mousedown event followed by a mouseup one, you can set mouseup value for the trigger property, which displays the tool tip:
$( "#cirkel1" ).each(function( i ) {
tippy(this, {
theme: 'blue',
trigger: 'mouseup', /* <-- here */
allowHTML: true,
placement: 'right',
animation: 'scale-subtle',
interactive: true,
content: function (reference) {
return reference.querySelector('.tooltipcontent');
}
});
The mouseup event will be fired if the mousedown occurred on the circle.
Working snippet.
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<base href="//printzelf.nl/new/">
<title>Image test</title>
<link rel="stylesheet" href="https://unpkg.com/tippy.js#6/animations/scale-subtle.css"/>
<link rel="stylesheet"href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
<script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous"></script>
</head>
<body>
<style media="screen">
.tippy-box {
width: 100%!important;
text-align: center;
background-color: #fff!important;
color: #fff!important;
box-shadow: 3px 2px 15px 6px rgb(0 0 0 / 10%);
}
.darktext {
color: #383838;
font-family: Panton;
font-size: 15px;
}
.tooltippadding {
padding: 15px;
}
body .tippy-arrow {
color: #fff!important;
}
</style>
<img id="background" src="https://static01.nyt.com/images/2019/05/29/realestate/00skyline-south4/88ce0191bfc249b6aae1b472158cccc4-superJumbo.jpg" alt="" style="display:none;">
<div class="canvas-container" style="width: 600px; height: 500px; position: relative; user-select: none;">
<canvas id="c" width="600" height="500" class="lower-canvas" style="border:1px solid red;position: absolute; width: 600px; height: 500px; left: 0px; top: 0px; touch-action: none; user-select: none;"></canvas>
</div>
</body>
<span id="cirkel1" class="carttip inlineflexmenu" style="border-radius:100%;width: 25px;height:25px;z-index:9999999;position:absolute;cursor:pointer;">
<div class="tooltipcontent darktext tooltippadding" style="position:relative;">
Testtest
</div>
</span>
<!-- Popper JS -->
<script src="assets/js/popper.min.js"></script>
<script src="https://unpkg.com/tippy.js#6"></script>
<script type="text/javascript" src="assets/js/fabric.js"></script>
<script type="text/javascript">
(function() {
var myImg = document.querySelector("#background");
var realWidth = myImg.naturalWidth;
var realHeight = myImg.naturalHeight;
var source = document.getElementById('background').src;
var canvas = new fabric.Canvas('c');
canvas.hoverCursor = 'pointer';
canvas.selection = false;
canvas.setDimensions({ width: realWidth, height: realHeight });
var img = new Image();
// use a load callback to add image to canvas.
img.src = 'https://static01.nyt.com/images/2019/05/29/realestate/00skyline-south4/88ce0191bfc249b6aae1b472158cccc4-superJumbo.jpg';
canvas.setBackgroundImage(source, canvas.renderAll.bind(canvas), {
backgroundImageOpacity: 0.5,
backgroundImageStretch: false
});
const hotspots = [
{
top: 140,
left: 230,
radius: 10,
fill: '#009fe3',
id: 'cirkel1',
hoverCursor: 'pointer',
selectable: false,
imgtop: 200,
imgleft: 300,
imgheight: 200,
imgwidth: 200,
tooltipid: 'cirkel1',
imgUrl: 'https://printzelf.nl/new/assets/images/logo_gewoon.png'
},
{
top: 240,
left: 530,
radius: 10,
fill: '#009fe3',
id: 'cirkel2',
hoverCursor: 'pointer',
selectable: false,
imgtop: 200,
imgleft: 700,
imgheight: 200,
imgwidth: 200,
imgUrl: 'https://i1.wp.com/nypost.com/wp-content/uploads/sites/2/2020/04/pugs-coronavirus.jpg'
},
{
top: 240,
left: 730,
radius: 10,
fill: '#009fe3',
id: 'cirkel2',
hoverCursor: 'pointer',
selectable: false,
imgtop: 200,
imgleft: 800,
imgheight: 200,
imgwidth: 200,
imgUrl: 'https://i.guim.co.uk/img/media/fe1e34da640c5c56ed16f76ce6f994fa9343d09d/0_174_3408_2046/master/3408.jpg?width=1200&height=900&quality=85&auto=format&fit=crop&s=0d3f33fb6aa6e0154b7713a00454c83d'
}
];
const loadedImages = [];
for (let [idx, props] of hotspots.entries()) {
let c = new fabric.Circle(props);
c.class = 'hotspot';
c.name = 'hotspot-' + idx;
canvas.add(c);
}
fabric.Canvas.prototype.getAbsoluteCoords = function(object) {
return {
left: object.left + this._offset.left,
top: object.top + this._offset.top
};
}
var cirkel1tooltip = document.getElementById('cirkel1'),
btnWidth = 40,
btnHeight = 40;
function positionBtn(obj) {
var absCoords = canvas.getAbsoluteCoords(obj);
cirkel1tooltip.style.left = (absCoords.left - btnWidth / 10) + 'px';
cirkel1tooltip.style.top = (absCoords.top - btnHeight / 10) + 'px';
}
for (const ho of canvas.getObjects()) {
// check for 'hotspot' class
if (ho.class && ho.class === 'hotspot') {
ho.on('mousedown', () => {
// check if image was previously loaded
if (loadedImages.indexOf(ho.name) < 0) {
// image is not in the array
// so it needs to be loaded
// prepare the image properties
let imgProps = {
width: ho.imgwidth,
height: ho.imgheight,
left: ho.imgleft,
top: ho.imgtop,
scaleX: .25,
scaleY: .25,
selectable: false,
id: 'img-' + ho.name,
hoverCursor: "default"
};
var printzelfImg = new Image();
printzelfImg.onload = function (img) {
var printzelf = new fabric.Image(printzelfImg, imgProps);
canvas.add(printzelf);
};
printzelfImg.src = ho.imgUrl;
// update the `loadedImages` array
loadedImages.push(ho.name);
} else {
// image was previously loaded
for (const o of canvas.getObjects()) {
// find the correct image on the canvas
if (o.id && o.id === 'img-' + ho.name) {
// toggle the visible property
o.visible = !o.visible;
break;
}
}
}
positionBtn(ho);
});
}
}
})();
$( "#cirkel1" ).each(function( i ) {
tippy(this, {
theme: 'blue',
trigger: 'mouseup',
allowHTML: true,
placement: 'right',
animation: 'scale-subtle',
interactive: true,
content: function (reference) {
return reference.querySelector('.tooltipcontent');
}
});
});
</script>
</html>

JQuery - Pause animation / add a next button

I've got a CodePen of what I've created (with the help of other sources) and everything looks great. I want the ability to click on the text (or the page) and pause the JQuery loop. As well as the ability to click it again to resume it. CodePen Link
If that's too hard, I would like to look at the Next button so I can manually toggle to the next line, as well as a manual toggle button to the main line of <li>#SSBASKETBALL | SUMMERSERIES.NZ</li>
The button needs to be at the bottom further down as it's part of OBS's Browser tool.
If anyone could help me out with this, please feel free. It does the job at the moment, but I would really like to incorporate it more professionally and have more control over it. Cheers!
(function($) {
$.simpleTicker = function(element, options) {
var defaults = {
speed: 800,
delay: 10000,
easing: 'swing',
effectType: 'fade'
}
var param = {
'ul': '',
'li': '',
'initList': '',
'ulWidth': '',
'liHeight': '',
'tickerHook': 'tickerHook',
'effect': {}
}
var plugin = this;
plugin.settings = {}
var $element = $(element),
element = element;
plugin.init = function() {
plugin.settings = $.extend({}, defaults, options);
param.ul = element.children('ul');
param.li = element.find('li');
param.initList = element.find('li:first');
param.ulWidth = param.ul.width();
param.liHeight = param.li.height();
element.css({
height: (param.liHeight)
});
param.li.css({
top: '0',
left: '0',
position: 'absolute'
});
switch (plugin.settings.effectType) {
case 'fade':
plugin.effect.fade();
break;
case 'roll':
plugin.effect.roll();
break;
case 'slide':
plugin.effect.slide();
break;
}
plugin.effect.exec();
}
plugin.effect = {};
plugin.effect.exec = function() {
param.initList.css(param.effect.init.css)
.animate(param.effect.init.animate, plugin.settings.speed, plugin.settings.easing)
.addClass(param.tickerHook);
setInterval(function() {
element.find('.' + param.tickerHook)
.animate(param.effect.start.animate, plugin.settings.speed, plugin.settings.easing)
.next()
.css(param.effect.next.css)
.animate(param.effect.next.animate, plugin.settings.speed, plugin.settings.easing)
.addClass(param.tickerHook)
.end()
.appendTo(param.ul)
.css(param.effect.end.css)
.removeClass(param.tickerHook);
}, plugin.settings.delay);
}
plugin.effect.fade = function() {
param.effect = {
'init': {
'css': {
display: 'block',
opacity: '0'
},
'animate': {
opacity: '1',
zIndex: '98'
}
},
'start': {
'animate': {
opacity: '0'
}
},
'next': {
'css': {
display: 'block',
opacity: '0',
zIndex: '99'
},
'animate': {
opacity: '1'
}
},
'end': {
'css': {
display: 'none',
zIndex: '98'
}
}
}
}
plugin.effect.roll = function() {
param.effect = {
'init': {
'css': {
top: '-3em',
display: 'block',
opacity: '0'
},
'animate': {
top: '0',
opacity: '1',
zIndex: '98'
}
},
'start': {
'animate': {
top: '3em',
opacity: '0'
}
},
'next': {
'css': {
top: '-3em',
display: 'block',
opacity: '0',
zIndex: '99'
},
'animate': {
top: '0',
opacity: '1'
}
},
'end': {
'css': {
zIndex: '98'
}
}
}
}
plugin.effect.slide = function() {
param.effect = {
'init': {
'css': {
left: (-(200)),
display: 'block',
opacity: '0'
},
'animate': {
left: '0',
opacity: '1',
zIndex: '98'
}
},
'start': {
'animate': {
left: (200),
opacity: '0'
}
},
'next': {
'css': {
left: (param.ulWidth),
display: 'block',
opacity: '0',
zIndex: '99'
},
'animate': {
left: '0',
opacity: '1'
}
},
'end': {
'css': {
zIndex: '98'
}
}
}
}
plugin.init();
}
$.fn.simpleTicker = function(options) {
return this.each(function() {
if (undefined == $(this).data('simpleTicker')) {
var plugin = new $.simpleTiecker(this, options);
$(this).data('simpleTicker', plugin);
}
});
}
})(jQuery);
$(function() {
$.simpleTicker($('#js-ticker-fade'), {
'effectType': 'fade'
});
$.simpleTicker($('#js-ticker-roll'), {
'effectType': 'roll'
});
$.simpleTicker($('#js-ticker-slide'), {
'effectType': 'slide'
});
});
#font-face {
font-family: "DharmaGothicEW01-Light";
src: url("https:https://db.onlinewebfonts.com/t/dcc1a03cbbd06fea7e48d65ff78624ef.eot");
src: url("https:https://db.onlinewebfonts.com/t/dcc1a03cbbd06fea7e48d65ff78624ef.eot?#iefix") format("embedded-opentype"), url("https://db.onlinewebfonts.com/t/dcc1a03cbbd06fea7e48d65ff78624ef.woff2") format("woff2"), url("https://db.onlinewebfonts.com/t/dcc1a03cbbd06fea7e48d65ff78624ef.woff") format("woff"), url("https://db.onlinewebfonts.com/t/dcc1a03cbbd06fea7e48d65ff78624ef.ttf") format("truetype"), url("https://db.onlinewebfonts.com/t/dcc1a03cbbd06fea7e48d65ff78624ef.svg#DharmaGothicEW01-Light") format("svg");
}
#font-face {
font-family: "DharmaGothicEW01-Bold";
src: url("https://db.onlinewebfonts.com/t/5c5772b491b3e1c39beb61efcd6824f7.eot");
src: url("https://db.onlinewebfonts.com/t/5c5772b491b3e1c39beb61efcd6824f7.eot?#iefix") format("embedded-opentype"), url("https://db.onlinewebfonts.com/t/5c5772b491b3e1c39beb61efcd6824f7.woff2") format("woff2"), url("https://db.onlinewebfonts.com/t/5c5772b491b3e1c39beb61efcd6824f7.woff") format("woff"), url("https://db.onlinewebfonts.com/t/5c5772b491b3e1c39beb61efcd6824f7.ttf") format("truetype"), url("https://db.onlinewebfonts.com/t/5c5772b491b3e1c39beb61efcd6824f7.svg#DharmaGothicEW01-Bold") format("svg");
}
* {
font-size: 32px;
word-spacing: 5px;
font-family: DharmaGothicEW01-Light;
text-align: center;
color: #000;
}
.hvhbox {
display: inline-block;
width: 50px;
border: 0px solid #000;
background: #fe3249;
text-align: center;
}
.simple-ticker {
position: relative;
text-align: center;
width: 100%;
padding: 30px;
border: 0px solid #ddd;
overflow: hidden;
}
.simple-ticker ul {
position: relative;
width: 100%;
margin: 0;
padding: 0;
list-style: none;
}
.simple-ticker ul li {
display: none;
width: 100%;
margin: 0;
padding: 0;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>jQuery simple news ticker</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<!-- partial:index.partial.html -->
<section>
<div class="simple-ticker" id="js-ticker-roll">
<ul>
<li>#SSBASKETBALL | SUMMERSERIES.NZ</li>
<li>THE SUMMER SERIES 20: <span style="background: #979797; color: #fff;"> HUTT VALLEY </span> VS <span style="background: #0053a3; color: #fff;"> UPPER HUTT </span></li>
<li>THE SUMMMER SERIES IS PROUDLY SUPPORTED BY TRIPLE THREAT</li>
<li><span style="background: #0053a3; color: #fff"> UHC </span> RJ WICHMAN 13 PTS</li>
<li><span style="background: #979797; color: #fff;"> HVH </span> 25% [1/4]  3PT PERCENTAGE  20% [1/5] <span style="background: #0053a3; color: #fff;"> UHC </span></li>
<!-- <li>NEXT UP: <span style="background: #fb1414; color: #fff;"> SACRED HEART </span> VS <span style="background: #fff200; color: #000;"> WELLINGTON EAST </span></li> -->
</ul>
</div>
</section>
<!-- partial -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js'></script>
<script src="./script.js"></script>
</body>
</html>

filters on Mapbox gl on existing example

Im trying to make a map with markers and filters from external geojson source, and the closest example i found is in this website http://bl.ocks.org/ryanbaumann/04c442906638e27db9da243f29195592
i removed the second source and im hoping to create more filters in checkboxes and dropdowns ONLY. My problem is the following:
when i choose from the dropdown filter it also shows the other markers but in smaller unclickable points. how can i make it show only the ones that are relevant for example when i choose " Pedestrian Fatalities" it will show only the ones that have a value there and not the others. Im not necesseraly looking for a code-based solution although it would be helpfull.
mapboxgl.accessToken = 'pk.eyJ1IjoicnNiYXVtYW5uIiwiYSI6IjdiOWEzZGIyMGNkOGY3NWQ4ZTBhN2Y5ZGU2Mzg2NDY2In0.jycgv7qwF8MMIWt4cT0RaQ';
var bounds = [
[-75.04728500751165, 39.68392799015035],
[-72.91058699000139, 41.87764500765852]
];
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v9',
center: [-74.0059, 40.7128],
zoom: 10,
minZoom: 9,
maxZoom: 18,
pitch: 0,
maxBounds: bounds
});
function init() {
map.addSource('veh-incidents-1', {
type: 'geojson',
data: 'https://dl.dropbox.com/s/z4hajzr32e59kv4/nyc_pedcyc_collisions_1.geojson?dl=0',
buffer: 0,
maxzoom: 12
});
if (window.location.search.indexOf('embed') !== -1) map.scrollZoom.disable();
map.addLayer({
'id': 'veh-incd-1',
'type': 'circle',
'source': 'veh-incidents-1',
'paint': {
'circle-color': {
property: 'CYC_INJ',
type: 'interval',
stops: [
[1, 'orange'],
[2, 'red']
]
},
'circle-radius': {
property: 'CYC_INJ',
base: 3,
type: 'interval',
stops: [
[1, 3],
[2, 8],
[3, 12]
]
},
'circle-opacity': 0.8,
'circle-blur': 0.5
},
'filter': ['>=', 'CYC_INJ', 1]
}, 'waterway-label');
map.addLayer({
'id': 'veh-incd-base-1',
'type': 'circle',
'source': 'veh-incidents-1',
'paint': {
'circle-color': 'yellow',
'circle-radius': 3,
'circle-opacity': 0.3,
'circle-blur': 1
},
'filter': ['<', 'CYC_INJ', 1]
}, 'waterway-label');
};
map.once('style.load', function(e) {
init();
map.addControl(new mapboxgl.NavigationControl());
map.on('click', function(e) {
var features = map.queryRenderedFeatures(e.point, {
layers: ['veh-incd-1']
});
if (!features.length) {
return;
}
var feature = features[0];
var popup = new mapboxgl.Popup()
.setLngLat(map.unproject(e.point))
.setHTML('<h3>Collision Detail</h3>' +
'<ul>' +
'<li>Year: <b>' + feature.properties.YEAR + '</b></li>' +
'<li>Pedestrian Injuries: <b>' + feature.properties.PED_INJ + '</b></li>' +
'<li>Pedestrian Fatalities: <b>' + feature.properties.PED_KIL + '</b></li>' +
'<li>Cyclist Injuries: <b>' + feature.properties.CYC_INJ + '</b></li>' +
'<li>Cyclist Fatalities: <b>' + feature.properties.CYC_KIL + '</b></li>' +
'</ul>')
.addTo(map);
});
//Hide loading bar once tiles from geojson are loaded
map.on('data', function(e) {
if (e.dataType === 'source' && e.sourceId === 'veh-incidents-1') {
document.getElementById("loader").style.visibility = "hidden";
}
})
// Use the same approach as above to indicate that the symbols are clickable
// by changing the cursor style to 'pointer'.
map.on('mousemove', function(e) {
var features = map.queryRenderedFeatures(e.point, {
layers: ['veh-incd-1']
});
map.getCanvas().style.cursor = (features.length) ? 'pointer' : '';
});
var prop = document.getElementById('prop');
prop.addEventListener('change', function() {
map.setPaintProperty('veh-incd-1', 'circle-color', {
property: prop.value,
type: 'interval',
stops: [
[1, 'green'],
[2, 'red']
]
});
map.setPaintProperty('veh-incd-1', 'circle-radius', {
property: prop.value,
base: 3,
type: 'interval',
stops: [
[1, 3],
[2, 6],
[3, 9]
]
});
map.setFilter('veh-incd-1', ['>=', prop.value, 1])
});
});
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title></title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.41.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.41.0/mapbox-gl.css' rel='stylesheet' />
<link href='https://www.mapbox.com/base/latest/base.css' rel='stylesheet' />
<style>
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
.map-overlay {
position: absolute;
width: 180px;
top: 0;
left: 10px;
padding: 10px;
margin-left: 5px;
margin-top: 2px;
margin-bottom: 2px;
margin-right: 5px;
z-index: 1;
}
.map-overlay .map-overlay-inner {
background: rgba(0, 0, 0, .8);
color: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.10);
border-radius: 3px;
padding: 10px;
margin-bottom: 10px;
z-index: 1;
}
.map-overlay-inner fieldset {
border: none;
padding: 0;
margin: 0 0 10px;
z-index: 1;
}
/* Dark attribution */
.mapboxgl-ctrl.mapboxgl-ctrl-attrib {
background: rgba(0, 0, 0, .8);
}
.mapboxgl-ctrl.mapboxgl-ctrl-attrib a {
color: #fff;
}
/* Dark popup */
.mapboxgl-popup-content {
background-color: #202020;
color: #fff;
margin-left: 5px;
margin-top: 2px;
margin-bottom: 2px;
margin-right: 5px;
z-index: 1000;
}
.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-tip,
.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-tip,
.mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip {
border-top-color: #202020;
}
.mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip,
.mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip,
.mapboxgl-popup-anchor-top .mapboxgl-popup-tip {
border-bottom-color: #202020;
}
.mapboxgl-popup-anchor-right .mapboxgl-popup-tip {
border-left-color: #202020;
}
.mapboxgl-popup-anchor-left .mapboxgl-popup-tip {
border-right-color: #202020;
}
#popup-menu ul,
#menu li {
margin: 0;
padding: 0;
z-index: 100;
}
.mapboxgl-ctrl-group {
-webkit-filter: invert(100%);
}
.loader {
margin: -10px 0 0 -250px;
height: 100px;
width: 20%;
position: fixed;
text-align: center;
padding: 1em;
top: 50%;
left: 50%;
margin: 0 auto 1em;
z-index: 9999;
}
/*
Set the color of the icon
*/
svg path,
svg rect {
fill: #FF6700;
}
</style>
</head>
<body>
<div id='map'></div>
<div class='map-overlay top'>
<div class='map-overlay-inner'>
<fieldset>
<label><b>Select property</b></label>
<select id='prop' name='prop'>
<option value='CYC_INJ'>Cyclist Injuries</option>
<option value='CYC_KIL'>Cyclist Fatalities</option>
<option value='PED_INJ'>Pedestrian Injuries</option>
<option value='PED_KIL'>Pedestrian Fatalities</option>
</select>
</fieldset>
<b>Kiosker</b>
</div>
</div>
<div class="loader loader--style1" title="0" id="loader">
<svg version="1.1" id="loader-1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="40px" height="40px" viewBox="0 0 40 40" enable-background="new 0 0 40 40" xml:space="preserve">
<path opacity="0.2" fill="#000" d="M20.201,5.169c-8.254,0-14.946,6.692-14.946,14.946c0,8.255,6.692,14.946,14.946,14.946
s14.946-6.691,14.946-14.946C35.146,11.861,28.455,5.169,20.201,5.169z M20.201,31.749c-6.425,0-11.634-5.208-11.634-11.634
c0-6.425,5.209-11.634,11.634-11.634c6.425,0,11.633,5.209,11.633,11.634C31.834,26.541,26.626,31.749,20.201,31.749z" />
<path fill="#000" d="M26.013,10.047l1.654-2.866c-2.198-1.272-4.743-2.012-7.466-2.012h0v3.312h0
C22.32,8.481,24.301,9.057,26.013,10.047z">
<animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0 20 20" to="360 20 20" dur="0.5s" repeatCount="indefinite" />
</path>
</svg>
</div>
</body>
</html>
The problem is in pointing the mouse pointer over a circle with a small radius, so use the queryRenderedFeatures function not with a point geometry, but with region geometry as an argument:
// https://www.mapbox.com/mapbox-gl-js/api/#map#queryrenderedfeatures
map.on('mousemove', function(e) {
var boxSize = new mapboxgl.Point(10, 10);
var sw = e.point.clone().add(boxSize);
var ne = e.point.clone().sub(boxSize);
var features = map.queryRenderedFeatures([sw, ne], {
layers: ['veh-incd-1']
});
map.getCanvas().style.cursor = (features.length) ? 'pointer' : '';
});
[ https://jsfiddle.net/5u8xgh61/ ]

Categories

Resources