The upgraded CSS determines the direction of the mouse

After a long voyage, the brave men swept away the demons and finally arrived at the stone tower where the devil (the interviewer) was located. The brave took on a challenge from The night watchman Chen Dayu head in front of the stone tower — using pure CSS difficulty to determine the direction of the mouse entering the box.

Chen Big fish head: “brave stay, before entering the stone tower, please use this challenge to practice hands!”

Given the initial HTML structure, write the code to complete the following functions:

Brave A: “Harm, this is not simple…”

Brave A positioned the four boxes in the four directions of up, down, left and right, and made animation with Hover state to complete the challenge.

The brave men had solved their problems and were enjoying themselves. After all the trials, nothing seems to be able to stop him from going after the demon king. They stepped confidently into the tower’s interior.

Then, dumbfounded.

Demon king: “brave people, did not solve this problem before, I gold body!” . (Refers to people who have to bend to the interviewer’s tough rules.)

The devil smiled and said: “HTML + CSS, I also don’t give the initial code, you can write whatever you want, anyway, to achieve the following thing.”


The picture effect

This is a manual dividing line, the following is the parsing.


parsing

The main idea is to use the selector to select the box that interacts with the mouse to the box that we want to trigger the effect, and attach animation properties.

However, after a search, found reality and ideal or a little gap.

CSS has no parent selector

I baidu out a variety of statements, said that the performance of the parent selector is poor, no browser manufacturers are willing to do, so the parent selector related standards have been delayed.

But I believe it is possible to fake a “parent selector” using some strange methods.

The first thing that comes to mind is that maybe some form related box can be combined with a CSS property selector to select the corresponding element, like the: Checked selector. But after some operation, there is no such thing that can meet the requirements.

Instead, the Form element can mark itself with the :focus-within state when its child component is in the :focus state. The problem is that it doesn’t tell us which sub-component is in focus, so we don’t know which box is interacting with the mouse.

After some thinking and trying, I chose the universal brother selector (here does not introduce the specific function of selector, do not know the students directly click the link to see MDN). Let’s get straight to the code structure.

HTML is as follows:

<div class="container">
    <! -- We need to insert some boxes here, interact with the mouse, and then select the eye with the "~.head. Eye" universal sibling selector -->
    <div class="head">
        <div class="face">
            <div class="mouth"></div>
            <div class="eye-group">
                <div class="eye eye-left"></div>
                <div class="eye eye-right"></div>
            </div>
        </div>
    </div>
</div>
Copy the code

Block_hoverer class tags are used to interact with the mouse at the head of Chen Dayu, the Night’s Watch.

The devil needs more here, as shown in the red area, where each rectangle is a box that interacts with the mouse:

Here I am “sticking” the box to my face using absolute positioning. There are three main considerations:

  • How to position the box on the circumference
  • How to determine the rotation Angle of the box itself
  • How to arrange the width and height of the box so that it completely covers the sides of the face

If the box itself doesn’t rotate, this weird thing happens:

Here we use a simple operation to rotate the box:

// The number of boxes that interact with the mouse
$part: 72;
// The Angle between each box
$part-degree: 360 / $part;

@for $i from 1 through $part {
    // The rotation Angle is offset by 90deg, which is affected by the positioning of the box.
    // This can be ignored, as long as we know that the principle is to determine the rotation Angle of the box according to the cycle.
    transform: rotate((90 + $i * $part-degree) + unquote('deg'));
}
Copy the code

About the third point “how to arrange the width and height of the box so that the box completely covers the face around”, it is an empirical thing, you manually adjust the width and height of the box, it is easy to figure out. The first point should be mentioned as an extra point, because CSS (SCSS) does not have sines and cosines, so you cannot use sines and cosines to position the box on the circle.

CSS doesn’t have sines and cosines, really?

(> _ >)

Well, no, but another thing that can be faked…

/** trigonometric function correlation */
/ * * @ see code from http://jimyuan.github.io/blog/2015/02/12/trigonometry-in-sass.html, thank you * /

@function fact($number) {
    $value: 1;
    @if $number>0{@for $i from 1 through $number {
            $value: $value * $i;
        }
    }
    @return $value;
}

@function pow($number.$exp) {
    $value: 1;
    @if $exp>0{@for $i from 1 through $exp {
            $value: $value * $number; }} @else if $exp< 0 {@for $i from 1 through -$exp {
            $value: $value / $number;
        }
    }
    @return $value;
}

@function rad($angle) {
    $unit: unit($angle);
    $unitless: $angle / ($angle * 0 + 1);
    @if $unit==deg {
        $unitless: $unitless / 180 * pi();
    }
    @return $unitless;
}

@function pi() {
    @return 3.14159265359;
}

@function sin($angle) {
    $sin: 0;
    $angle: rad($angle);
    // Iterate a bunch of times.
    @for $i from 0 through 10 {
        $sin: $sin + pow(-1.$i) * pow($angle, (2 * $i + 1)) / fact(2 * $i + 1);
    }
    @return $sin;
}

@function cos($angle) {
    $cos: 0;
    $angle: rad($angle);
    // Iterate a bunch of times.
    @for $i from 0 through 10 {
        $cos: $cos + pow(-1.$i) * pow($angle.2 * $i) / fact(2 * $i);
    }
    @return $cos;
}
Copy the code

With trigonometric functions and a loop arrangement, you can make a lot of things:

// Width of face
$face-width: 300;
// The number of boxes that interact with the mouse
$part: 72;
// The Angle between each box
$part-degree: 360 / $part;

@for $i from 1 through $part {

    /* Calculate the position of the box on the circumference. It should be noted that the width and height of the outer box and some offsets corresponding to its width and height should be added. * /
    $angle: ($i / $part) * 2 * 3.1416;
    $x: cos($angle) * $face-width / 2 + 500;
    $y: sin($angle) * $face-width / 2;

    // Familiar :nth-child selector. I already talked about this in my last blog post. Check it out!
    .box_hover:nth-child(#{$i{})left: $x + unquote('px');
        top: $y + unquote('px');
        transform: rotate((90 + $i * $part-degree) + unquote('deg'));

        // Different boxes :hover state, which changes something in its sibling. Head box. This concerns how the eye is made, which will be discussed later.
        &:hover ~ .head {
            $ty: sin($angle) * $face-width / 50;
            $tx: cos($angle) * $face-width / 50;
            left: calc(50%- # {$tx}px);
            top: calc(50%- # {$ty}px);
            .eye {
                &:after {
                    background-position: 100% 50%;
                    transform: rotate(
                        (0 + $i * $part-degree) + unquote('deg')); } } } } }Copy the code

Finally, the eye.

The first idea must be to use absolute positioning, and then hover different boxes, set different left and top values for the eyes.

However, this is not feasible because once the eyes are animated, once the mouse moves very fast and the left and top values change in a straight line, the eye movements will become strange (♂).

But that didn’t work because I didn’t want to write any more math, so I went back to the rotate fascinating little thing.

When there are no hover boxes, we draw a circle in the center of the.eye class box (where the circle is in the center of the box) :

After hovering a box, we rotate the.eye box and change the position of the circle (which is in the right center of the box) :

And then you’re done.

Don’t have the face to ask for a like. = ^, ェ, (o) o

The complete code is posted below, you can also go to my blog kangkang specific implementation (nuggets personal home page side has a small website button, click that can directly hint attention!) .

The source code

<div class="container">
    <! This line is VueJS syntax, notice -->
    <div class="circle" v-for="item in 72"></div>
    <div class="shadows"></div>
    <div class="head">
        <div class="face">
            <div class="mouth"></div>
            <div class="eye-group">
                <div class="eye eye-left"></div>
                <div class="eye eye-right"></div>
            </div>
        </div>
    </div>
</div>
Copy the code
Trigonometric functions @ see http://jimyuan.github.io/blog/2015/02/12/trigonometry-in-sass.html / * * * /

@function fact($number) {
    $value: 1;
    @if $number>0{@for $i from 1 through $number {
            $value: $value * $i;
        }
    }
    @return $value;
}

@function pow($number.$exp) {
    $value: 1;
    @if $exp>0{@for $i from 1 through $exp {
            $value: $value * $number; }} @else if $exp< 0 {@for $i from 1 through -$exp {
            $value: $value / $number;
        }
    }
    @return $value;
}

@function rad($angle) {
    $unit: unit($angle);
    $unitless: $angle / ($angle * 0 + 1);
    @if $unit==deg {
        $unitless: $unitless / 180 * pi();
    }
    @return $unitless;
}

@function pi() {
    @return 3.14159265359;
}

@function sin($angle) {
    $sin: 0;
    $angle: rad($angle);
    // Iterate a bunch of times.
    @for $i from 0 through 10 {
        $sin: $sin + pow(-1.$i) * pow($angle, (2 * $i + 1)) / fact(2 * $i + 1);
    }
    @return $sin;
}

@function cos($angle) {
    $cos: 0;
    $angle: rad($angle);
    // Iterate a bunch of times.
    @for $i from 0 through 10 {
        $cos: $cos + pow(-1.$i) * pow($angle.2 * $i) / fact(2 * $i);
    }
    @return $cos;
}

/ * * * * * * * * * * * * * * * * * * * * * * * smile * /
/* I changed the address based on a project in CodePen

$container-height: 500;

.container {
    position: relative;
    width: 1000px;
    height: $container-height + unquote('px');
    overflow: hidden;
    background: #feee9d;
}
.container{{*position: absolute;
    }
    *:not(.circle):before,
    *:not(.circle):after {
        content: ' ';
        position: absolute;
    }

    $face-width: 300;
    $circle-width: $container-height;

    /** Listener code */

    .circle {
        position: absolute;
        width: 30px;
        height: $circle-width + unquote('px');
        // &:hover {
        // background: red;
        // }
    }
    $part: 72;
    $part-degree: 360 / $part;
    @for $i from 1 through $part {
        $angle: ($i / $part) * 2 * 3.1416;
        $x: cos($angle) * $face-width / 2 - 5 + 500;
        $y: sin($angle) * $face-width / 2;
        .circle:nth-child(#{$i{})left: $x + unquote('px');
            top: $y + unquote('px');
            transform: rotate((90 + $i * $part-degree) + unquote('deg'));
            &:hover ~ .head {
                $ty: sin($angle) * $face-width / 50;
                $tx: cos($angle) * $face-width / 50;
                left: calc(50%- # {$tx}px);
                top: calc(50%- # {$ty}px);
                .eye {
                    &:after {
                        background-position: 100% 50%;
                        transform: rotate(
                            (0 + $i * $part-degree) + unquote('deg')); } } } } }/** Style code */

    .shadows..head {
        border-radius: 50%;
        width: $face-width + unquote('px');
        height: $face-width + unquote('px');
        transform: translate(-50%, -50%);
        top: calc(50%);
        left: calc(50%);
        cursor: pointer;
    }
    .shadows {
        background-color: darken(#fbd671.20%);
    }
    .head {
        background-color: #fbd671;
    }

    .face {
        width: 150px;
        height: 170px;
        top: 75px;
        left: 75px;
    }

    .mouth {
        width: 100%;
        height: 70px;
        bottom: 0;
        background-color: #20184e;
        border: 5px solid #20184e;
        border-radius: 150px 150px 10px 10px;
        overflow: hidden;
        &:after {
            background-color: #f15962;
            width: 100px;
            height: 60px;
            left: 20px;
            top: 40px;
            border-radius: 50%; }}.eye-group {
        top: 10px;
        width: 150px;
        height: 50px;
        .eye {
            width: 40px;
            height: 40px;
            background-color: #20184e;
            border-radius: 50%;
            border: 5px solid #20184e;
            &:after {
                width: 100%;
                height: 100%;
                top: 0;
                left: 0;
                background: radial-gradient(#fbd671 68%.#20184e 68%);
                background-size: 10px 10px;
                background-repeat: no-repeat;
                background-position: 50% 50%;
                transition: 0.1 s;
            }
            &.eye-left {
                left: 15px;
            }
            &.eye-right {
                right: 15px; }}}}Copy the code

Afterword.

Yesterday I commented on Chen’s article “I can use a 10×10 grid system to build a more accurate device”.

My original idea was to implement something like this, see the following image (pure CSS) :

rendering

parsing

But since CSS has no parent selector, this implementation is almost useless, as you can see from my HTML structure.

<div class="container">
    <p class="info">Box outside</p>
    <div class="boxes">
        <div class="box" v-for="item in 5">
            <div class="box-inner">
                <div class="left" />
                <div class="right" />
                <! The --.des class is a container for the changing text in the diagram. Change the text content by changing the content of its pseudo-element. -->
                <p class="des"></p>
            </div>
        </div>
    </div>
    <p class="info">The inside of the box</p>
</div>
Copy the code

So limit is very big, each with mouse interaction under the container to show effect of placing a box (. Each box class deposit box below. There is a separate des category box, the des box repeated many times), it can achieve a higher degree of precision the mouse into the direction judgment, but has brought a lot of duplicate code, It doesn’t help.

By the way, the CSS code in both examples in this article performs poorly, and when the number of elements interacting with the mouse increases to around 200, my computer starts to get visually stuck, something that is not available in a production environment, so it’s just a matter of dealing with the devil.

Well, it’s kind of fun, isn’t it? (● strap strap ●) Fun is right!