Can't do JS-in-CSS for css-houdini paint? - css

I'm trying to copy this example; I have a scss file that I'm using with modular CSS in a React/Electron project. I want to define the function to be used by paint in the same file, as in the example:
.container {
--background-canvas: (ctx, geom) => {
// left blank for minimal example
};
background-image: paint(background-canvas);
display: flex;
margin: 4px;
border-radius: 12px;
height: 75px;
}
However, this fails to compile with CssSyntax error: Expected a pseudo-class or pseudo-element. (2:23). What am I not doing that the demo is?

Alright, I got it mostly working. The only part that isn't working is the transition which I'm not sure why it isn't.
-- Edit: I got that working via: https://css-houdini.rocks/animating-gradient
CSS.registerProperty({
name: '--multiplier',
syntax: '<number>',
inherits: false,
initialValue: 0
})
I couldn't find a way to get the CSS in JS parser to treat { } as a part of a string rather than special characters, so I used an array to allow me to run the relevant function calls in the background-canvas function.
--background-canvas: (ctx, geom) => [ ctx.moveTo(0, 0),
ctx.lineTo(
var(--pad) + (geom.width - var(--slant) - var(--pad)) * var(--multiplier),
0
),
ctx.lineTo(
var(--pad) + (geom.width - var(--slant) - var(--pad)) * var(--multiplier) +
var(--slant),
geom.height
),
ctx.lineTo(0, geom.height), ctx.fillStyle = \`var(--color)\`, ctx.fill() ];
The real fun part about this solution is that you still need to actually register the paint function.
I did that in a similar way as a previous answer I have: https://stackoverflow.com/a/61966697/13175138 which uses this https://twitter.com/DasSurma/status/983305990731894785
As a note, this solution from that example uses eval as part of it's registerPaint function, so this could be problematic from a security standpoint, though the paint code should theoretically be sandboxed from the main runtime.
const Demo = styled.div`
background: #1108a0;
padding: 50px 0;
`;
const Test = styled.div`
--color: cyan;
--multiplier: 0.24;
--pad: 30;
--slant: 20;
--background-canvas: (ctx, geom) => [ ctx.moveTo(0, 0),
ctx.lineTo(
var(--pad) + (geom.width - var(--slant) - var(--pad)) * var(--multiplier),
0
),
ctx.lineTo(
var(--pad) + (geom.width - var(--slant) - var(--pad)) * var(--multiplier) +
var(--slant),
geom.height
),
ctx.lineTo(0, geom.height), ctx.fillStyle = \`var(--color)\`, ctx.fill() ];
background: paint(background-canvas);
transition: --multiplier 0.4s;
font: bold 6em sans-serif;
color: yellow;
text-shadow: 0 3px 1px cyan;
line-height: 1.5em;
width: max-content;
padding-left: 30px;
padding-right: 50px;
isolation: isolate;
&:hover {
--multiplier: 1;
}
& span {
mix-blend-mode: exclusion;
}
`;
const App = () => (
<Demo>
<Test className="el" right={'right'}>
<span>JS-in-CSS</span>
</Test>
</Demo>
);
ReactDOM.render(<App />, document.getElementById('root'));
/*if ("paintWorklet" in CSS) {
console.log('here')
const src = document.querySelector('script[language$="paint"]').innerHTML;
const blob = new Blob([src], {
type: 'text/javascript'
});
CSS.paintWorklet.addModule(URL.createObjectURL(blob));
}*/
#media (max-width: 900px) {
.el {
font-size: 4em;
}
}
#media (max-width: 500px) {
.el {
font-size: 2.4em;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/react-is#17.0.1/umd/react-is.production.min.js"></script>
<script src="https://unpkg.com/styled-components/dist/styled-components.min.js"></script>
<div id="root"/>
<script language="javascript+paint">
registerPaint('background-canvas', class {
static get inputProperties() {
return ['--background-canvas'];
}
paint(ctx, geom, properties) {
eval(properties.get('--background-canvas').toString())(ctx, geom, properties);
}
})
</script>
<script>
// Register the property so it become animatable
CSS.registerProperty({
name: '--multiplier',
syntax: '<number>',
inherits: false,
initialValue: 0
})
if ("paintWorklet" in CSS) {
const src = document.querySelector('script[language$="paint"]').innerHTML;
const blob = new Blob([src], {
type: 'text/javascript'
});
CSS.paintWorklet.addModule(URL.createObjectURL(blob));
}
</script>

Related

How to Add a Space in Line Numbers in Textarea Word Wrap in Svelte

I have a text area with CSS generated line numbers for each new line.
Everything works great, except for when there is a word wrap. If I get a word wrap, it does not wrap the new line text. I would like to make my line numbers word wrap with the text.
Here is a notepad+ example:
And here is a GitHub example:
How can I automatically detect and add a word wrap to my line numbers as well? Here is my code:
<script lang="ts">
export let source = `jon
sup
me
fixin to do sumthin good`;
let numberOfLines = source.split('\n').length;
const KeyUp = (event: Event) => {
const textarea = event.target as HTMLInputElement;
numberOfLines = textarea.value.split('\n').length;
};
const KeyDown = (event: Event) => {
const textarea = event.target as HTMLInputElement;
if ((event as KeyboardEvent).key === 'Tab') {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
if (start && end) {
textarea.value = textarea.value.substring(0, start) + '\t'
+ textarea.value.substring(end);
event.preventDefault();
}
}
};
</script>
<center>
<div class="editor">
<div class="line-numbers">
{#each Array(numberOfLines) as _}
<span class="new-line" />
{/each}
</div>
<textarea class="text-content"
on:keyup={KeyUp} on:keydown={KeyDown} value={source} rows="15" />
</div>
</center>
<style>
.editor {
display: inline-flex;
gap: 10px;
font-family: monospace;
line-height: 21px;
background: #282a3a;
border-radius: 2px;
padding: 20px 10px;
}
.line-numbers {
width: 20px;
text-align: right;
}
.line-numbers .new-line {
counter-increment: linenumber;
}
.line-numbers .new-line::before {
content: counter(linenumber);
display: block;
color: #506882 !important;
}
.text-content {
line-height: 21px;
overflow-y: hidden;
padding: 0;
border: 0;
background: #282a3a;
color: #fff;
min-width: 500px;
outline: none;
resize: none;
}
</style>
And here is the StackBlitz Link:
(right click - open in new window)
Thanks!
J
Image for responding to comment below:
An approach could be to read the width of one character of the monospace font from an invisible element and set the textarea width to a multiple to avoid rounding errors. It then can be calculated for each substring between the line breaks how often it is wrapped and a margin-bottom added to the corresponding line number accordingly
REPL
<script>
import {onMount} from 'svelte'
export let source = `jon\nsup there is some other text here that I want to go across the lines and not word wrap another line etc boy!\nme\nfixin to do sumthin good`;
let fontWidthElem
let fontWidth
let charactersPerLine = 60
$: areaWidth = charactersPerLine * fontWidth
onMount(() => {
fontWidth = fontWidthElem.getBoundingClientRect().width
})
function wrapped(str, timesWrapped) {
if(str.length > charactersPerLine) {
const firstLine = str.substring(0, charactersPerLine)
const spaceOrDashInFirstLine = /[ -]/.test(firstLine)
const lastCharFirstLine = str[charactersPerLine-1]
const firstCharNextLine = str[charactersPerLine]
const wordIsCut = lastCharFirstLine !== '-' && lastCharFirstLine !== ' ' && firstCharNextLine !== ' ' && firstCharNextLine !== '-'
const nextLine = str.substring(charactersPerLine)
const nextLineNotOnlySpaces = nextLine.replaceAll(' ', '').length > 0
if(wordIsCut && spaceOrDashInFirstLine){
const lastIndexOfDivider = Math.max(firstLine.lastIndexOf(' '), firstLine.lastIndexOf('-'))
return wrapped(str.substring(lastIndexOfDivider+1), timesWrapped+1)
}else if(nextLineNotOnlySpaces){
return wrapped(nextLine.trimStart(), timesWrapped+1)
}else {
return timesWrapped
}
}else {
return timesWrapped
}
}
</script>
<div class="editor">
<div class="line-numbers">
{#each source.split('\n') as subStr, index}
<div class="line-nr"
style:margin-bottom="calc(var(--line-height) * {wrapped(subStr, 0)})"
>
{index + 1}
</div>
{/each}
</div>
<textarea bind:value={source}
style:width="{areaWidth}px"
rows="15"
/>
<div id="font-width"
bind:this={fontWidthElem}
>
a
</div>
</div>
<style>
:global(body) {
padding: 0;
}
.editor {
--line-height: 21px;
position: relative;
display: inline-flex;
gap: 10px;
padding: 20px 10px;
font-family: monospace;
line-height: var(--line-height);
background: #282a3a;
border-radius: 2px;
}
.line-nr {
width: 20px;
text-align: right;
color: #506882 !important;
}
textarea {
line-height: var(--line-height);
overflow-y: hidden;
margin: 0;
padding: 0;
border: 0;
background: #282a3a;
color: #fff;
outline: none;
resize: none;
}
#font-width {
position: absolute;
top: -1000px;
left: -1000px;
visibility: hidden;
}
</style>
To make the width responsive
additional editor wrapper for padding
watch editor width and calculate maximal possible charactersPerLine for textarea besides line numbers element
charactersPerLine added as argument to wrapped() for recalculation on change
REPL
<script>
import {onMount} from 'svelte'
export let source = `jon\nsup there is some other text here that I want to go across the lines and not word wrap another line etc boy!\nme\nfixin to do sumthin good`;
let fontWidthElem
let fontWidth
let editorWidth
onMount(() => {
fontWidth = fontWidthElem.getBoundingClientRect().width
})
let lineNumbersWidth = 30
$: charactersPerLine = Math.floor((editorWidth - lineNumbersWidth) / fontWidth)
function wrapped(str, charactersPerLine, timesWrapped) {
if(str.length > charactersPerLine) {
const firstLine = str.substring(0, charactersPerLine)
const spaceOrDashInFirstLine = /[ -]/.test(firstLine)
const lastCharFirstLine = str[charactersPerLine-1]
const firstCharNextLine = str[charactersPerLine]
const wordIsCut = lastCharFirstLine !== '-' && lastCharFirstLine !== ' ' && firstCharNextLine !== ' ' && firstCharNextLine !== '-'
const nextLine = str.substring(charactersPerLine)
const nextLineNotOnlySpaces = nextLine.replaceAll(' ', '').length > 0
if(wordIsCut && spaceOrDashInFirstLine){
const lastIndexOfDivider = Math.max(firstLine.lastIndexOf(' '), firstLine.lastIndexOf('-'))
return wrapped(str.substring(lastIndexOfDivider+1), charactersPerLine, timesWrapped+1)
}else if(nextLineNotOnlySpaces){
return wrapped(nextLine.trimStart(), charactersPerLine, timesWrapped+1)
}else {
return timesWrapped
}
}else {
return timesWrapped
}
}
</script>
<div class="editor-wrapper">
<div class="editor"
bind:clientWidth={editorWidth}
>
<div class="line-numbers"
style:width="{lineNumbersWidth}px"
>
{#each source.split('\n') as subStr, index}
<div class="line-nr"
style:margin-bottom="calc(var(--line-height) * {wrapped(subStr, charactersPerLine, 0)})"
>
{index + 1}
</div>
{/each}
</div>
<textarea bind:value={source}
style:width="{charactersPerLine}ch"
rows="15"
/>
<div id="font-width"
bind:this={fontWidthElem}
>
a
</div>
</div>
</div>
<style>
:global(body) {
padding: 0;
}
:global(*) {
box-sizing: border-box;
}
.editor-wrapper {
--line-height: 21px;
padding: 20px 10px;
background: #282a3a;
border-radius: 2px;
}
.editor {
position: relative;
display: flex;
width: 100%;
font-family: monospace;
line-height: var(--line-height);
}
.line-numbers {
padding-right: 10px;
text-align: right;
color: #506882 !important;
}
textarea {
line-height: var(--line-height);
overflow-y: hidden;
margin: 0;
padding: 0;
border: 0;
background: #282a3a;
color: #fff;
outline: none;
resize: none;
/* border seems to falsify calculation! */
/* box-shadow: 0 0 0 1px grey; */
}
#font-width {
position: absolute;
top: -1000px;
left: -1000px;
visibility: hidden;
}
</style>

How to set ScrollToTop Button to be active on viewport height?

At the moment i am using hardcoded height point to trigger visible ScrollToTop Button.
i would love to get solution to be triggered when passing viewport height.
const { scrollDirection } = useScrollDirection()
const { scrollPosition } = useScrollPosition()
const [isVisible, setIsVisible] = useState(false)
const toggleVisible = () => {
if (scrollPosition === 0) {
setIsVisible(false)
}
**if (scrollPosition > 800) {
setIsVisible(true)
} else if (scrollPosition <= 799) {
setIsVisible(false)
}**
}
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: "smooth",
})
}
window.addEventListener("scroll", toggleVisible)
you can use window.innerHeight
const toggleVisible = () => {
const viewportHeight = window.innerHeight;
if (scrollPosition === 0) {
setIsVisible(false)
}
**if (scrollPosition > viewportHeight) {
setIsVisible(true)
} else if (scrollPosition <= viewportHeight) {
setIsVisible(false)
}**
}
You can do this by using Intersection Observer (IO)
First you create an element that is just below the viewport initially. And whenever this element comes into view, show the button.
This requires one dummy element which you observe, for the demo I set the html element to position: relative for it to work. Maybe you can use a different element structure, based on your html. Important thing is that you have one element you can observe and trigger the element depending on when it comes into view.
let options = {
rootMargin: '0px',
threshold: 0.1 // when at least 10% of the element is visible we show the button
}
const callback = (entries, observer) => {
const btn = document.querySelector('#scroll-top');
entries.forEach(entry => {
if (entry.intersectionRatio > 0.1) {
// if we are past our 0.1 threshold we show the button
btn.classList.add('visible')
} else {
// otherwise we hide the button
btn.classList.remove('visible')
}
});
};
const observer = new IntersectionObserver(callback, options);
const target = document.querySelector('#button-trigger');
observer.observe(target);
.dummy-viewport {
min-height: 400vh;
}
html {
position: relative;
}
#button-trigger {
position: absolute;
top: 100vh;
left: 10px;
height: calc(100% - 100vh);
/* for demo purposes, don't show the element on the finished site*/
width: 2rem;
outline: 1px solid rebeccapurple;
writing-mode: vertical-rl;
text-orientation: mixed;
}
p {
padding: 0;
margin: 0;
}
#scroll-top {
position: fixed;
bottom: 40px;
right: 10px;
opacity: 0;
transition: .5s opacity;
}
#scroll-top.visible {
opacity: 1
}
<div class="dummy-viewport">
<p> Scroll down ↓ </p>
<button id="scroll-top" type="button"> Scroll to top </button>
</div>
<div id="button-trigger">
<p> When I am visible, I show the button </p>
</div>

How to change CSS classes according to time received by API

I'm building a weather app in React and so far so good. The problem is now I want to have a "lightmode" and "darkmode" which should be CSS classes that change according to sunrise/sunset times received by an API. When I did it in vanilla JS I used a function that converted the timestamps into hours and compared the current hour to sunrise/sunset and then decided which class to present, like so
function getMode(response) {
let today = response.data.dt;
let timezone = response.data.timezone;
let difference = today + timezone - 3600;
let hours = timeConverter(difference);
let mode = document.getElementById("app");
let sunrise = response.data.sys.sunrise;
let difference2 = sunrise + timezone - 3600;
let currentSunrise = timeConverter(difference2);
let sunset = response.data.sys.sunset;
let difference3 = sunset + timezone - 3600;
let currentSunset = timeConverter(difference3) - 1;
if (hours > currentSunset) {
mode.classList.add("darkmode").remove("lightmode");
}
else if (hours < currentSunrise) {
mode.classList.add("darkmode").remove("lightmode");
} else {
mode.classList.remove("darkmode").add("lightmode");
}
}
axios.get(apiUrl).then(getMode)
<body>
<div id="app" class="lightmode">
The CSS then looked like this
.lightmode h1 {
font-family: "Josefin Sans", sans-serif;
text-align: right;
color: #06384d;
font-size: 32px;
font-weight: 700;
}
.lightmode {
font-family: "Josefin Sans", sans-serif;
background-image: linear-gradient(120deg, #a1c4fd 0%, #c2e9fb 100%);
border-style: solid;
border-radius: 30px;
border-color: #096386;
}
#app {
margin: 10px 400px;
padding: 10px 10px;
}
(...)
.darkmode h1 {
font-family: "Josefin Sans", sans-serif;
text-align: right;
color: #fff;
font-size: 32px;
font-weight: 700;
}
.darkmode {
font-family: "Josefin Sans", sans-serif;
background-image: linear-gradient(to top, #30cfd0 0%, #330867 100%);
border-style: solid;
border-radius: 30px;
border-color: #096386;
}
And that worked fine. Now in React (novice here) I don't know how to approach the problem. I've been reading about dynamically changing CSS classes in React with state but I can't figure out how to incorporate that with the API response. Any suggestions?
You can store className in state and change it in your function.
class Demo extends Component {
constructor(props) {
super(props);
this.state = {
stClass: "lightmode"
}
}
state = {
stClass: "lightmode"
}
componentDidMount = () => {
[call your function here and change the state of stClass]
}
render() {
return (
<div className={`${this.state.stClass}`}>
[your code here]
</div>
)
}
}
The key part of dynamically changing CSS classes in React would be:
<div className={`App ${this.state.displayMode}`}>
The class name of the container will be updated anytime the state for displayMode is changed and in this example, appended to the class App, resulting in App darkmode and rendered as such.
<div class="App darkmode">
Sample code / use case:
class App extends Component {
state = {
displayMode: "lightmode"
};
getMode = response => {
let _displayMode;
// Logic for determining the mode
return _displayMode;
}
componentDidMount() {
// Make API call here and run your logic to determine the mode
axios.get(apiUrl).then(getMode).then(displayMode => {
// As a callback to your function, set the state for displayMode
this.setState({
displayMode: displayMode
})
});
}
render() {
return (
<div className={`App ${this.state.displayMode}`}>
</div>
);
}
}

Add custom bindTooltip class

I am trying to add custom class to my bindTooltip but the new class do not show up. My method based on this question.
My custom popup class is working fine but if I want to overwrite the tooltip class than it is now working.
My JS code:
var PopupClass={'className': 'class-popup'}
var TooltipClass={'className': 'class-tooltip'}
L.marker(
[46.17319713, 21.34458608],
{icon: OnlineMarker}
).bindPopup(
'Test Popup',
PopupClass
).bindTooltip(
'Test Tooltip',
{direction: 'top', permanent: true, offset: [10,0]},
TooltipClass
).addTo(MyMap)
My CSS code:
/* popup-class*/
.class-popup .leaflet-popup-content-wrapper {
background:#2980b9;
color:#fff;
font-size:10px;
line-height:10px;
}
.class-popup .leaflet-popup-content-wrapper a {
color:#2980b9;
}
.class-popup .leaflet-popup-tip-container {
width:40px;
height:20px;
}
.class-popup .leaflet-popup-tip {
background:#2980b9;
}
/* tooltip-class*/
.class-tooltip{
background: green;
border: 2px solid cyan
}
.leaflet-tooltip-left.class-tooltip::before {
border-left-color: cyan;
}
.leaflet-tooltip-right.class-tooltip::before {
border-right-color: cyan;
}
You have 2 issues:
You try to specify your Tooltip class using a 3rd argument of .bindTooltip, which does not do anything as per Leaflet doc. Instead, you should merge your className key in the 2nd argument (options). For that, you can either:
write it directly within the options
extend your TooltipClass with your options: L.Util.extend(myOptions, TooltipClass)
use the ES2018 spread operator to do the same as the above point.
Your .class-tooltip selector in CSS is not enough to override the default Leaflet style. Increase your selector specificity, e.g. adding the Leaflet tooltip class: .leaflet-tooltip.class-tooltip
var MyMap = L.map('map').setView([46.17319713, 21.34458608], 11);
var PopupClass = {
'className': 'class-popup'
}
var TooltipClass = {
'className': 'class-tooltip'
}
L.marker([46.17319713, 21.34458608])
.bindPopup('Test Popup', PopupClass)
.bindTooltip('Test Tooltip', {
direction: 'top',
permanent: true,
offset: [10, 0],
//'className': 'class-tooltip'
...TooltipClass // using spread operator (ES2018)
}, TooltipClass) // 3rd argument does not do anything
.addTo(MyMap);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(MyMap);
/* popup-class*/
.class-popup .leaflet-popup-content-wrapper {
background: #2980b9;
color: #fff;
font-size: 10px;
line-height: 10px;
}
.class-popup .leaflet-popup-content-wrapper a {
color: #2980b9;
}
.class-popup .leaflet-popup-tip-container {
width: 40px;
height: 20px;
}
.class-popup .leaflet-popup-tip {
background: #2980b9;
}
/* tooltip-class*/
.leaflet-tooltip.class-tooltip {
background: green;
border: 2px solid cyan
}
.leaflet-tooltip-left.class-tooltip::before {
border-left-color: cyan;
}
.leaflet-tooltip-right.class-tooltip::before {
border-right-color: cyan;
}
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.3.1/dist/leaflet.css" integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ==" crossorigin="" />
<script src="https://unpkg.com/leaflet#1.3.1/dist/leaflet-src.js" integrity="sha512-IkGU/uDhB9u9F8k+2OsA6XXoowIhOuQL1NTgNZHY1nkURnqEGlDZq3GsfmdJdKFe1k1zOc6YU2K7qY+hF9AodA==" crossorigin=""></script>
<div id="map" style="height: 180px"></div>

Animate a quizz app with AngularJS

I had done one quiz application, But i want to add some animations
like fadein/fade-out, when click the prev/next button. Can any one
help me do the same. something need to change the css something need to change the CSS something need to change the css something need to change the css?
* {}
body {}
.question {
width: 70%;
margin: 0 auto;
height: auto;
display: block;
background: #eeeeee;
}
.question h1 {
text-align: center;
padding-top: 30px;
color: #666666;
}
.question h2 {
width: 100%;
font-size: 22px;
color: #0c1e5c;
padding: 1% 3% 0% 3%;
}
.question ul:nth-child(odd) {
background: #d0dff6;
width: 30%;
padding: 8px;
margin: 1% 9%;
display: inline-block;
color: #0c1e5c;
}
.question ul:nth-child(even) {
background: #d0dff6;
width: 30%;
padding: 8px;
margin: 1% 9%;
display: inline-block;
color: #0c1e5c;
}
.button {
text-align: center;
margin: 1% 0;
}
.btn {
background: #8bf8a7;
padding: 5px;
}
<html ng-app="quiz">
<head>
<meta charset="utf-8">
<title>Basic Quiz</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.2/angular.min.js"></script>
<link href="style.css" rel="stylesheet" type="text/css">
</head>
<body ng-controller="quizCtrl">
<div class="question">
<h1>QUIZ APPLICATION</h1>
<h2>{{questions.question}}</h2>
<ul ng-repeat="option in questions.options">
<li style="list-style:none">
<input type="{{buttonType}}">{{option.text}}</li>
</ul>
</div>
<div class="button">
<input type="button" value="previous" class="btn" ng-show="isPrevious" ng-click="previousQuestion()">
<input type="button" value="next" class="btn" ng-show="isNext" ng-click="nextQuestion()">
</div>
</body>
<script>
var app = angular.module("quiz", [])
app.controller("quizCtrl", function($scope) {
$scope.data = [{
question: "1)Which of the following selector matches a element based on its id?",
type: "single",
options: [{
text: "The Id Selector"
},
{
text: "The Universal Selector"
},
{
text: "The Descendant Selector"
},
{
text: "The Class Selector"
}
]
},
{
question: "2)Which of the following defines a measurement as a percentage relative to another value, typically an enclosing element?",
type: "multiple",
options: [{
text: "%"
},
{
text: "cm"
},
{
text: "percentage"
},
{
text: "ex"
}
]
},
{
question: "3)Which of the following property is used to set the background color of an element?",
type: "single",
options: [{
text: "background-color"
},
{
text: "background-image"
},
{
text: "background-repeat"
},
{
text: "background-position"
}
]
},
{
question: "4)Which of the following is a true about CSS style overriding?",
type: "multiple",
options: [{
text: "Any inline style sheet takes highest priority. So, it will override any rule defined in tags or rules defined in any external style sheet file."
},
{
text: "Any rule defined in tags will override rules defined in any external style sheet file."
},
{
text: "Any rule defined in external style sheet file takes lowest priority, and rules defined in this file will be applied only when above two rules are not applicable."
}
]
}
];
$scope.index = 0;
$scope.questions = $scope.data[$scope.index];
$scope.buttonType = $scope.questions.type == 'single' ? 'radio' : 'checkbox';
$scope.isPrevious = false;
$scope.isNext = true;
$scope.nextQuestion = function() {
if ($scope.index < 3) {
$scope.index = $scope.index + 1;
$scope.questions = $scope.data[$scope.index];
$scope.buttonType = $scope.questions.type == 'single' ? 'radio' : 'checkbox';
$scope.isPrevious = true;
if ($scope.index == 3) {
$scope.isNext = false;
}
} else {
// disble next botton logic
$scope.isNext = false;
}
}
$scope.previousQuestion = function() {
if ($scope.index > 0) {
$scope.index = $scope.index - 1;
$scope.questions = $scope.data[$scope.index];
$scope.buttonType = $scope.questions.type == 'single' ? 'radio' : 'checkbox';
$scope.isNext = true;
if ($scope.index == 0) {
$scope.isPrevious = false;
}
} else {
// disble next botton logic
$scope.isPrevious = false;
}
}
});
</script>
</html>
Check out ng-animate, basically what it does is it adds classes that you can style accordingly on showing dom and on hiding dom, like this:
/* The starting CSS styles for the enter animation */
.fade.ng-enter {
transition:0.5s linear all;
opacity:0;
}
/* The finishing CSS styles for the enter animation */
.fade.ng-enter.ng-enter-active {
opacity:1;
}
And to use that functionality you would have to use ng-repeat in your html, something like this:
<div ng-repeat="item in data" ng-if="index === $index">
//Your question html here
</div>
Where data and index are $scope.data and $scope.index.
That would be the angular way of doing things.
However I see that you are using the same div, only changing scope data, that would require you to set
transition: 1s all ease;
On the question class, and then to do something like this in javascript:
angular.element('.question').css('opacity', 0);
$timeout(function() {
// change question..
angular.element('.question').css('opacity', 1);
}, 1000);

Resources