Skip to content

Commit ad65b45

Browse files
authored
Merge pull request #4638 from Arjunsivakumar28/wv-2058
WV-2058: Add click animation when URL parameter is passed
2 parents 763f80e + 98a2aee commit ad65b45

File tree

4 files changed

+103
-0
lines changed

4 files changed

+103
-0
lines changed

src/App.jsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import webAppConfig from './js/config';
2525
import VoterStore from './js/stores/VoterStore';
2626
import initializeFacebookSDK from './js/utils/initializeFacebookSDK';
2727
import RouterV5SendMatch from './js/utils/RouterV5SendMatch';
28+
import flashCursorClickListener from './js/common/utils/flashCursorClickListener';
2829
// importRemoveCordovaListenersToken1 -- Do not remove this line!
2930

3031
// Root URL pages
@@ -226,6 +227,10 @@ class App extends Component {
226227

227228
this.acceptURLVariables();
228229
this.bypass2FA();
230+
231+
if (AppObservableStore.getFlashCursorEnabled()) {
232+
this.detatchCursorListener = flashCursorClickListener();
233+
}
229234
}
230235

231236
componentDidUpdate (prevProps) {
@@ -243,6 +248,9 @@ class App extends Component {
243248
componentWillUnmount () {
244249
this.appStateSubscription.unsubscribe();
245250
this.voterStoreListener.remove();
251+
if (this.detatchCursorListener) {
252+
this.detatchCursorListener();
253+
}
246254
// removeCordovaListenersToken -- Do not remove this line!
247255
}
248256

@@ -367,6 +375,8 @@ class App extends Component {
367375
const fromAd = query.get('ad');
368376
const showOfficeBannerAboveHeader = query.get('office_intro');
369377
const showEditPoliticianNotice = query.get('show_edit_politician_notice');
378+
const normalizedQuery = new URLSearchParams(queryString.toLowerCase());
379+
const flashCursor = normalizedQuery.get('flashcursor');
370380
if (fromAd === '1' && !fromAdSet) {
371381
this.setState({ fromAdSet: true });
372382
Cookies.set('ad_url_variable_used', '1', { expires: 15, path: '/' });
@@ -380,6 +390,10 @@ class App extends Component {
380390
this.setState({ showOfficeBannerAboveHeaderSet: true });
381391
AppObservableStore.setShowOfficeBannerAboveHeader(true);
382392
}
393+
if (flashCursor === '1') {
394+
this.setState({ flashCursor: true });
395+
AppObservableStore.setFlashCursorEnabled(true);
396+
}
383397
}
384398

385399
bypass2FA () {

src/css/main.css

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4833,3 +4833,36 @@ a {
48334833
min-width:100%!important;
48344834
width:100%!important
48354835
}
4836+
4837+
/* ripple click overlay */
4838+
.flashCursorOverlay {
4839+
position: fixed;
4840+
inset: 0;
4841+
pointer-events: none;
4842+
z-index: 2147483647;
4843+
}
4844+
4845+
/* ripple element from click */
4846+
.clickRipple {
4847+
position: fixed;
4848+
width: 18px; /* initial size */
4849+
height: 18px;
4850+
border-radius: 9999px;
4851+
border: 2px solid rgba(0, 154, 250, 0.9); /* ripple color */
4852+
transform: translate(-50%, -50%) scale(0.2);
4853+
opacity: 0.9;
4854+
will-change: transform, opacity; /* change scale and opacity */
4855+
animation: clickRippleOut 0.5s ease-out forwards; /* ripple and fade */
4856+
}
4857+
4858+
/* Expanding + fading ripple */
4859+
@keyframes clickRippleOut {
4860+
0% {
4861+
transform: translate(-50%, -50%) scale(0.2);
4862+
opacity: 0.9;
4863+
}
4864+
100% {
4865+
transform: translate(-50%, -50%) scale(3);
4866+
opacity: 0;
4867+
}
4868+
}

src/js/common/stores/AppObservableStore.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const nonFluxState = {
3535
chosenWebsiteName: '',
3636
currentPathname: '',
3737
drawerOpenDict: {},
38+
flashCursorEnabled: false,
3839
getStartedMode: '',
3940
getVoterGuideSettingsDashboardEditMode: '',
4041
googleAnalyticsEnabled: false,
@@ -174,6 +175,9 @@ export default {
174175
}
175176
return false;
176177
},
178+
getFlashCursorEnabled () {
179+
return nonFluxState.flashCursorEnabled;
180+
},
177181

178182
getPoliticianWeVoteIdBeingViewed () {
179183
return nonFluxState.politicianWeVoteIdBeingViewed;
@@ -490,6 +494,9 @@ export default {
490494
nonFluxState.drawerOpenDict = updatedDrawerOpenDict;
491495
messageService.sendMessage('state updated drawerOpenDict');
492496
},
497+
setFlashCursorEnabled (flashCursorEnabled) {
498+
nonFluxState.flashCursorEnabled = flashCursorEnabled;
499+
},
493500

494501
setEvaluateHeaderDisplay () {
495502
// Force the Header to evaluate whether it should display
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// check if overlay exists, if not create one
2+
function cursorOverlayExists () {
3+
let overlay = document.querySelector('#flashCursorOverlay');
4+
if (!overlay) {
5+
overlay = document.createElement('div');
6+
overlay.id = 'flashCursorOverlay';
7+
overlay.className = 'flashCursorOverlay';
8+
document.body.appendChild(overlay);
9+
}
10+
return overlay;
11+
}
12+
13+
// check if ripple object exists
14+
function rippleCircle () {
15+
let ripple = document.querySelector('#clickRipple');
16+
if (!ripple) {
17+
ripple = document.createElement('div');
18+
ripple.id = 'clickRipple';
19+
ripple.className = 'clickRipple';
20+
}
21+
return ripple;
22+
}
23+
24+
export default function flashCursorClickListener () {
25+
const overlay = cursorOverlayExists();
26+
const onClick = (e) => {
27+
// get mouse position
28+
const x = e.clientX;
29+
const y = e.clientY;
30+
31+
// set ripple div on mouse location
32+
const el = rippleCircle();
33+
el.style.left = `${x}px`;
34+
el.style.top = `${y}px`;
35+
36+
overlay.appendChild(el);
37+
window.setTimeout(() => {
38+
el.remove();
39+
}, 600);
40+
};
41+
42+
// begin event listener
43+
document.addEventListener('click', onClick, true);
44+
45+
// return function to remove event listener
46+
return () => {
47+
document.removeEventListener('click', onClick, true);
48+
};
49+
}

0 commit comments

Comments
 (0)