Zenn 소개 이미지 캡처

개시하다


zen의이 페이지에서 원절소전 이미지 기능을 실현했다.

여기와 같은 일을 하고 싶었는데 여러 가지 시행착오의 결과로 이루어졌기 때문에 소개를 부탁드리고 싶었어요.
※ 또한, 이 기사는 제가 직접 조사하여 이와 같은 내용을 실현하였습니다!나는 이 점을 깨닫고 이 보도를 쓰고 있다.따라서 Zenn이 내부적으로 실제로 어떤 조치를 취했는지 양해해 주십시오.

[시연] 소개 이미지의 캡처


소개 이미지의 잘라내기에 대한 프레젠테이션입니다.
이것은 이번에 실시한 것이다.

결론


바로 결론을 얻었습니다. [Cropper.js]라는 프로그램 라이브러리를 사용하면 실현할 수 있습니다.

  • 사이트 축소판 그림
    https://fengyuanchen.github.io/cropper.js/

  • GitHub
    https://github.com/fengyuanchen/cropperjs
  • 상세히 설명하다


    상세한 설명에 들어가기 전에 전제로 이번에는 Nuxt.js를 사용하여 제품 개발을 진행했습니다.Nuxt.js에 대한 부분적인 설명도 포함되어 있으니 양해해 주십시오.다른 프레임을 사용하는 사람은 적당히 자신의 환경으로 바꿔서 읽을 수 있다.

    Cropper.js 설치 및 읽기


    먼저, 크롭퍼.js(yarn 또는 npm를 설치하거나 자신의 환경에 따라 분리해서 사용)를 설치합니다.
    yarn add cropperjs
    
    npm install cropperjs
    
    설치가 완료되면 프로젝트에서 js 파일과 css 파일을 읽습니다.
    여기도 당신의 환경으로 바꿔 주세요.
    <script lang="ts">
    import Cropper from "cropperjs";
    <script>
    
    <style>
    @import "cropperjs/dist/cropper.min.css";
    </style>
    

    css 설정


    해야 할 일은 대략 다음과 같은 두 가지가 있다.
  • 이미지 절단 부분과 관련된 설정 증가
  • 모드에 표시된 이미지의 높이와 너비의 비율을 1대 1로 설정
  • 추가

    1. 이미지 클립 부분과 관련된 설정 추가


    여기에 다음 기술을 추가하면 됩니다.
    .cropper-view-box,
    .cropper-face {
      border-radius: 50%;
      cursor: grab;
      outline: initial;
    }
    .cropper-face:active {
      cursor: grabbing;
    }
    
    여기에는 다음 4가지가 설정되어 있습니다.
  • 이미지 절단 부분을 둥글게 만드는 설정
  • 이미지를 마우스로 복사할 때 커서 설정
  • 이미지를 선택할 때 커서 설정
  • 대강 초기화
  • 1. 이미지 클립 부분을 동그랗게 만들기 위한 설정
    Cropper.기본적으로 js는 그림을 사각형으로 자릅니다.
    그림% 1개의 캡션을 편집했습니다.
  • Cropper.사이트 축소판 그림
    https://fengyuanchen.github.io/cropper.js/examples/crop-a-round-image.html
  • 2.이미지를 롤오버할 때 커서 설정
    3.이미지를 선택할 때 커서 설정
    Cropper.js 기본값은 "cursor:all scroll"입니다.
    커서에 대한 자세한 내용은 다음 URL을 참조하십시오(이미지 선택 시 설정도 추가됨).
  • 커서 정보
    https://saruwakakun.com/html-css/basic/cursor
  • 4. 개요 초기화
    Cropper.기본적으로 js의 윤곽은 파란색이기 때문에 초기화됩니다.
    상기 css 설정을 추가합니다.
    또한, 다음에 지정한 ".cropper-*"라는 class 속성은 Cropper입니다.js에서 사용하기 때문에 여기서 바로 복사할 수 있습니다.

    2. 모드에 표시된 이미지의 높이와 너비의 비율을 1대 1로 설정하는 설정 증가


    [태그]를 사용하여 선택한 이미지를 표시하고 해당 태그의 높이와 너비의 배율을 1대 1로 늘립니다.
    zen에서 높이(height)와 너비(width)는 모두 300px(2021/5/27 현재)로 설정됩니다.
    300px라면 스펀지 패드에 대응할 때 화면 사이즈 변경 시 거리 측정이 필요 없다는 장점이 있고 특별한 문제가 없다는 점을 고려해 300px를 스스로 설정했다.
    사실 내가 쓴 글씨와는 좀 다르지만 인상은 이렇다.
    <img id="cropping-image" :src="fileURL" style="width: 300px; height: 300px;"/>
    
    이렇게 하면 아래 그림과 같이 높이, 너비가 300px로 표시됩니다.

    Cropper 구성


    Cropper의 설정에 대한 설명을 하기 전에 모드의 vue 파일의 script 라벨에 대한 기술을 아래에 기재합니다(일부 기사에서도 필요하지 않은 부분이 있으니 양해해 주십시오).
    ※ 이 vu 파일은 component입니다.
    <script lang="ts">
    import Cropper from "cropperjs";
    import Vue from "vue";
    const zoomRatio = 0.05 as number;
    export default Vue.extend({
      props: {
        fileURL: {
          type: String,
          default: null,
        },
      },
      data() {
        return {
          cropper: null as any,
          zoomRangeValue: 0 as number,
        };
      },
      mounted() {
        const croppingImage: HTMLImageElement = <HTMLImageElement>(
          document.getElementById("cropping-image")
        );
        this.cropper = new Cropper(croppingImage, {
          aspectRatio: 1,
          viewMode: 3,
          cropBoxResizable: false,
          cropBoxMovable: false,
          dragMode: "move",
          guides: false,
          center: false,
          highlight: false,
          rotatable: false,
          toggleDragModeOnDblclick: false,
          restore: false,
          zoomOnWheel: false,
          // imgのHeightとWidthに合わせる(正方形にするといい感じに表示される)。
          minCropBoxHeight: 300,
          minCropBoxWidth: 300,
        });
      },
      computed: {
        zoomImage: {
          get(): number {
            return this.zoomRangeValue;
          },
          set(value: number) {
            // 値の大小比較を行う。
            // 変更後の値の方が大きかったらプラス(ズームイン)。
            // 変更後の値の方が小さかったら、プラス(ズームアウト)。
            let beforeValue = this.zoomImage;
            let afterValue = value;
    
            // なぜか、9と10の比較がうまくできないのでこのような条件分岐で対応。
            if (beforeValue == 9 && afterValue == 10) {
              this.cropper.zoom(zoomRatio);
            } else if (beforeValue == 10 && afterValue == 9) {
              this.cropper.zoom(-zoomRatio);
            } else if (beforeValue < afterValue) {
              this.cropper.zoom(zoomRatio);
            } else if (beforeValue > afterValue) {
              this.cropper.zoom(-zoomRatio);
            }
            // 値をセットする。
            this.zoomRangeValue = value;
          },
        },
      },
      methods: {
        confirmCroppingImage() {
          // 「Cropper」型に一致しているかどうかを確認する。
          if (this.cropper instanceof Cropper) {
            let croppedCanvas = this.cropper.getCroppedCanvas({
              width: 160,
              height: 160,
            });
            let roundedCanvas = this.getRoundedCanvas(croppedCanvas);
            // 元の画面にDataURLを返す。
            this.$emit("imageFileUrl", roundedCanvas.toDataURL());
            // モーダルを閉じる。
            this.$emit("closeTheProfileImageModal");
          }
        },
        getRoundedCanvas(sourceCanvas: HTMLCanvasElement) {
          let canvas: HTMLCanvasElement = document.createElement("canvas");
          if (canvas !== undefined) {
            let context = canvas.getContext("2d");
            let width = sourceCanvas.width;
            let height = sourceCanvas.height;
    
            canvas.width = width;
            canvas.height = height;
    
            if (context !== null) {
              context.imageSmoothingEnabled = true;
              context.drawImage(sourceCanvas, 0, 0, width, height);
              context.globalCompositeOperation = "destination-in";
              context.beginPath();
              context.arc(
                width / 2,
                height / 2,
                Math.min(width, height) / 2,
                0,
                2 * Math.PI,
                true
              );
              context.fill();
            }
          }
          return canvas;
        },
      },
    });
    </script>
    
    Cropper의 설정은 다음과 같습니다.
      mounted() {
        const croppingImage: HTMLImageElement = <HTMLImageElement>(
          document.getElementById("cropping-image")
        );
        this.cropper = new Cropper(croppingImage, {
          aspectRatio: 1,
          viewMode: 3,
          cropBoxResizable: false,
          cropBoxMovable: false,
          dragMode: "move",
          guides: false,
          center: false,
          highlight: false,
          rotatable: false,
          toggleDragModeOnDblclick: false,
          restore: false,
          zoomOnWheel: false,
          // imgのHeightとWidthに合わせる(正方形にするといい感じに表示される)。
          minCropBoxHeight: 300,
          minCropBoxWidth: 300,
        });
      }
    
    여기서 실시 상황을 다음과 같이 설명한다.
    "document.getElementById"를 사용하여 HTML 태그를 가져오고 "new Cropper"의 첫 번째 매개변수에 전달하며 두 번째 매개변수에 다양한 옵션을 설정합니다.
    옵션에 대한 자세한 내용은 다음 URL을 확인하십시오(이 편에서 자세한 설명은 생략).
  • "Cropper.js"옵션에 대한 설명
    https://cly7796.net/blog/javascript/try-using-cropper-js/
  • 여기서 가장 중요한 점은'minCropBox Height'와'minCropBox Width'다.
    이 설정을 "2. 모드에 표시된 이미지의 높이와 너비의 비례가 1대 1인 설정"부분에서 설정한 "Hegiht"와 "width"와 일치시키십시오.
    이렇게 하면 그림을 캡처한 부분이 많은 창으로 확장됩니다.
    이 설정의 전후는 다음과 같다.
    왼쪽은 설정 전, 오른쪽은 설정 후.

    슬라이더 설치


    슬라이더 설계


    슬라이더는 HTML5의 input 요소를 사용합니다.
    input 요소는 다음과 같다.
    <input type="range" id="range" min="0" max="10" step="1" v-model="zoomImage" />
    
    디자인 부분에 관해서는CatNose씨(zen의 개발자)가 쓴 아래의 글을 참고하도록 하겠습니다.
  • CSS에서 슬라이더의 디자인 변경
    https://code-kitchen.dev/html/input-range/
  • 기본적으로 복제된 것이다.
    변경점으로서 색상과 관련된 부분만 있습니다.
    자신이 쓴 소스 코드도 먼저 아래에 쓰세요.
    input[type="range"] {
      -webkit-appearance: none; /* 🚩これ無しだとスタイルがほぼ全く反映されないので注意 */
      appearance: none;
      cursor: pointer; /* カーソルを分かりやすく */
      outline: none; /* スライダーのアウトラインは見た目がキツイので消す */
      height: 14px; /* バーの高さ */
      width: 100%; /* バーの幅 */
      background: #dac4f7; /* バーの背景色 */
      border-radius: 10px; /* バーの両端の丸み */
      border: solid 3px #f1dfff; /* バー周囲の線 */
    }
    /* WebKit向けのつまみ */
    input[type="range"]::-webkit-slider-thumb {
      -webkit-appearance: none; /*  🚩デフォルトのつまみのスタイルを解除 */
      background: #671cc9; /* 背景色 */
      width: 24px; /* 幅 */
      height: 24px; /* 高さ */
      border-radius: 50%; /* 円形に */
      box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.15); /* 影 */
    }
    /* Moz向けのつまみ */
    input[type="range"]::-moz-range-thumb {
      background: #671cc9; /* 背景色 */
      width: 24px; /* 幅 */
      height: 24px; /* 高さ */
      border-radius: 50%; /* 円形に */
      box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.15); /* 影 */
      border: none; /* デフォルトの線を消す */
    }
    /* Firefoxで点線が周りに表示されてしまう問題の解消 */
    input[type="range"]::-moz-focus-outer {
      border: 0;
    }
    /* つまみをドラッグしているときのスタイル */
    input[type="range"]:active::-webkit-slider-thumb {
      box-shadow: 0px 5px 10px -2px rgba(0, 0, 0, 0.3);
    }
    

    슬라이더 이벤트


    여기 있습니다.
        zoomImage: {
          get(): number {
            return this.zoomRangeValue;
          },
          set(value: number) {
            // 値の大小比較を行う。
            // 変更後の値の方が大きかったらプラス(ズームイン)。
            // 変更後の値の方が小さかったら、プラス(ズームアウト)。
            let beforeValue = this.zoomImage;
            let afterValue = value;
    
            // なぜか、9と10の比較がうまくできないのでこのような条件分岐で対応。
            if (beforeValue == 9 && afterValue == 10) {
              this.cropper.zoom(zoomRatio);
            } else if (beforeValue == 10 && afterValue == 9) {
              this.cropper.zoom(-zoomRatio);
            } else if (beforeValue < afterValue) {
              this.cropper.zoom(zoomRatio);
            } else if (beforeValue > afterValue) {
              this.cropper.zoom(-zoomRatio);
            }
            // 値をセットする。
            this.zoomRangeValue = value;
          },
    
    슬라이더의 값이 변경된 시점에서 변경 전의 값과 변경된 후의 값을 비교한다.
    그리고 변경 전의 값이 작으면 확대하고 변경 전의 값이 크면 초점화 출력을 한다.
    크롭퍼입니다.js에서 준비한 줌 방법을 사용하면 OK.
    매개 변수로 전달된'줌 라티오'는 이번에는 0.5다.
    이 프로그램을 봤어, 응?어?그렇게 생각해요, 여러분.네, 보시다시피.
    'beforeValue=9&afterValue===10'과'beforeValue=10&afterValue=9'등 늘 필요하지 않다고 생각하는 조건의 구분이 적혀 있다.
    슬라이더의 값이 9에서 10으로 바뀌거나 10에서 9로 바뀔 때 값의 크기를 잘 비교하지 못해 추가했습니다.
    아마도 내가 어딘가에서 실시했을 뿐,,,, 도저히 해결할 수 없을 것 같아서 이렇게 했습니다.

    "확인" 단추를 눌렀을 때의 처리


    확인 버튼을 누를 때의 처리는 다음과 같습니다.
    <button class="button is-primary" @click="confirmCroppingImage()">
    確定
    </button>
    
      methods: {
        confirmCroppingImage() {
          // 「Cropper」型に一致しているかどうかを確認する。
          if (this.cropper instanceof Cropper) {
            let croppedCanvas = this.cropper.getCroppedCanvas({
              width: 160,
              height: 160,
            });
            let roundedCanvas = this.getRoundedCanvas(croppedCanvas);
            // 元の画面にDataURLを返す。
            this.$emit("imageFileUrl", roundedCanvas.toDataURL());
            // モーダルを閉じる。
            this.$emit("closeTheProfileImageModal");
          }
        },
        getRoundedCanvas(sourceCanvas: HTMLCanvasElement) {
          let canvas: HTMLCanvasElement = document.createElement("canvas");
          if (canvas !== undefined) {
            let context = canvas.getContext("2d");
            let width = sourceCanvas.width;
            let height = sourceCanvas.height;
    
            canvas.width = width;
            canvas.height = height;
    
            if (context !== null) {
              context.imageSmoothingEnabled = true;
              context.drawImage(sourceCanvas, 0, 0, width, height);
              context.globalCompositeOperation = "destination-in";
              context.beginPath();
              context.arc(
                width / 2,
                height / 2,
                Math.min(width, height) / 2,
                0,
                2 * Math.PI,
                true
              );
              context.fill();
            }
          }
          return canvas;
        },
      },
    
    는 여기서 먼저 크롭퍼의 정보에 따라 높이와 너비의 비율이 1대 1인 캔버스 요소를 생성한다.
    그런 다음 canvas 요소를 원형으로 잘라 DataURL을 생성한 다음 원래 페이지에 전달합니다.
    그리고 마지막으로 모형을 닫습니다.
    여기에 기재되지는 않았지만 DataURL을 받은 원래 페이지에 라벨의 src 속성에 DataURL이 설정되어 있습니다.이렇게 하면 동그랗게 캡처된 이미지가 표시됩니다.
    여기에 생성된 canvas 요소의 높이(height)와 너비(width)는 모드가 설정한 높이와 일치하지 않아도 된다(높이와 너비의 비는 1대1이면 된다).
    또한 getRoundedCanvas 메서드는 다음 URL을 참조합니다.
  • Cropper.사이트 축소판 그림
    https://fengyuanchen.github.io/cropper.js/examples/crop-a-round-image.html
  • 총결산


    Zenn이 실시한 프로필 이미지의 편집 방법에 대해 조사한 결과 같은 것을 발견하여 총괄하였다.
    가장 골절적인 것은 해시태그의'Height''Width'와 Cropper'minCropBox Height''minCropBox Width'를 조합해야 한다는 것이다.
    여기서는 도무지 눈치채지 못했는데 좌절할 뻔했지만 무사히 이렇게 써줘서 다행이다.
    제 생각에는 프로필 사진을 동그랗게 자르면 키가 많이 필요하지 않을까 싶어요. 참고를 좀 해주면 좋겠어요.

    좋은 웹페이지 즐겨찾기