반응형

2024.0415 업데이트 내용

- 디자인 수정

- 휴일 체크시 입력부분 Disable처리

- 버그 수정 및 코드 정리

 

------------------

 

다니는 회사가 자율 출퇴근이고 근무시간을 관리하는 HR이 있긴 하지만 조금 불편한 부분이 있어서 만들어보았다.

모바일 대응을 하긴 했는데 UX적으로 세로 스크롤이 길어지는 바람에 더 불편한 느낌이다. 나중에 개선을 좀 해야할것같고.

머 기본적으로 사용하는데는 문제는 없는것같다.

출근시간이 입력되어있고 퇴근시간이 입력되지 않은 첫번째 요일의 퇴근시간을 계산해 준다. 원래는 금요일 퇴근시간만 생각하고 만들었다가 금요일 휴일이면 어떻하지 하면서 개발이 수정되었다.

하루 기본 8시간 근무로 주 40시간을 채워야 하고, 하루 최대 근무 인정시간은 9시간으로 한시간을 누적시킬 수 있어서 금요일 전까지 최대 4시간을 누적시키면 (9+9+9+9) 36시간을 채울 수 있고 금요일은 4시간 근무만 하고 퇴근할 수 있다.

경우의 수가 좀 많다보니 코드가 아직은 완벽하지 않지만 조금씩 업데이트 해 나갈 생각이다.

 

테스트 페이지 링크

Github 링크

 

일주일 개인 근무시간 관리

일주일 개인 근무시간 관리 요일 출근 시간 퇴근 시간 휴가 시간 휴일 근무 인정 시간(최대 9시간) 적립시간 퇴근 가능 시간 계산 모든 설정 리셋

h9interaction.github.io

 

index.html

<!DOCTYPE html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>일주일 개인 근무시간 관리</title>
    <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
    <link rel="icon" href="/favicon.ico" type="image/x-icon">
    <link rel="stylesheet" href="style.css?v=0.0.9">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
    <link rel="stylesheet" as="style" crossorigin
        href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"
        integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/timepicker/jquery.timepicker.min.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/timepicker/jquery.timepicker.min.css">
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

</head>

<body>
    <div calss="content">
        <div class="title-center">일주일 개인 근무시간 관리</div>
        <div class="chart-container">
            <canvas id="workTimeChart"></canvas>
            <div class="total-remainning-time" id="total-remainning-time">
                -
            </div>
        </div>
        <div class="table-container">
            <table id="workHoursTable">
                <thead>
                    <tr>
                        <th class="table-title first">요일</th>
                        <th class="table-title second">출근 시간</th>
                        <th class="table-title third">퇴근 시간</th>
                        <th class="table-title forth">휴가 시간</th>
                        <th class="table-title fifth">휴일</th>
                        <th class="table-title sixth">근무 인정 시간(최대 9시간)</th>
                        <th class="table-title seventh">적립시간</th>
                    </tr>
                </thead>
                <tbody>
                    <!-- JavaScript를 통해 동적으로 행 추가 -->
                </tbody>
            </table>
        </div>
        <div class="action-container">
            <button onclick="calculatedayExitTime()">퇴근 가능 시간 계산</button>
            <button class="reset-all" onclick="resetAll()">모든 설정 리셋</button>
            <div id="dayExitTime"></div>
        </div>

        <script src="script.js?v=0.0.9"></script>
    </div>
</body>

</html>

 

 

script.js

const color1 = '#0ACFD9';
const color2 = '#F67777';
const color3 = '#666666';
const color4 = '#FFFFFF';
const color5 = '#f3f3f3';
const color6 = '#B068FF';
const color7 = '#F6F3FA';
const chartColor11 = '#C8A6F1'; // 휴일 색상
const chartColor22 = '#9CE4F1'; // 근무 인정 시간 색상
const chartColor33 = '#F7E58B'; // 휴가 시간 색상
const chartColor44 = '#F79999'; // 남은 시간 색상

let remainingMinutes;

window.onload = function () {
    makeTable();

    $('.timepicker-input').timepicker({
        'scrollDefault': 'now',
        'timeFormat': 'H:i A',
        'step': 1,
        'disableTimeRanges': [
            ['00:00 AM', '08:00 AM'],
            ['09:30 PM', '11:59 PM']
        ]
    });
    updateWorkHours();
};

// make chart
var ctx = document.getElementById('workTimeChart').getContext('2d');
var workTimeChart = new Chart(ctx, {
    type: 'doughnut', // 원형 그래프 유형
    data: {
        labels: ['휴일', '근무 인정 시간', '휴가 시간', '남은 시간'],
        datasets: [{
            label: '근무 시간',
            data: [0, 0, 0, 0], // 첫 번째 값은 인정된 근무 시간, 두 번째 값은 남은 근무 시간(40시간 중에서)
            backgroundColor: [
                chartColor11, // 휴일 색상
                chartColor22, // 근무한 시간 색상
                chartColor33,
                chartColor44 // 남은 시간 색상
            ],
            borderColor: [
                color4,
                color4,
                color4,
                color4
            ],
            borderWidth: 0
        }]
    },
    options: {
        responsive: true, // 차트가 컨테이너 크기에 맞춰 조정.
        plugins: {
            legend: {
                display: true, // 범례 표시 설정
                position: 'bottom', // 범례의 위치
            },
            tooltip: { // Chart.js 3.x 이상에서는 tooltip을 사용
                callbacks: {
                    label: function (context) {
                        let label = context.label || '';
                        if (label) {
                            label += ': ';
                        }
                        if (context.parsed !== undefined) {
                            const value = context.parsed;
                            label += formatMinutesAsHours(value);
                        }
                        return label;
                    }
                }
            }
        }
    }
});

function makeTable() {
    const days = ['월', '화', '수', '목', '금'];
    const titles = ['요일', '출근 시간', '퇴근 시간', '휴가 시간', '휴일 체크', '인정 시간(최대 9시간)', '적립시간'];
    const tableBody = document.getElementById('workHoursTable').getElementsByTagName('tbody')[0];
    days.forEach((day, index) => {
        let row = tableBody.insertRow();
        row.insertCell(0).innerText = day;
        row.cells[0].setAttribute('data-title', titles[0]);
        row.cells[0].classList.add('title');
        row.cells[0].style.backgroundColor = color5;
        row.cells[0].style.fontWeight = '700';
        row.classList.add('day-row');
        row.setAttribute('data-day', day);
        for (let i = 1; i <= 6; i++) { // 셀 추가로 인덱스 6까지 확장
            let cell = row.insertCell(i);
            cell.setAttribute('data-title', titles[i]);
            cell.classList.add('title');
            cell.classList.add(day);
            if (i < 3) { // 출근 시간과 퇴근 시간 입력란
                let input = document.createElement('input');
                input.type = 'text';
                input.inputMode = 'numeric';
                input.pattern = '[0-9]*';
                input.className = 'timepicker-input';

                if (i === 1) {
                    input.value = localStorage.getItem(`startTime${index + 1}`) || '';
                }
                if (i === 2) {
                    input.value = localStorage.getItem(`endTime${index + 1}`) || '';
                }
                let resetBtn = document.createElement('button');
                resetBtn.className = 'btn item-reset';
                resetBtn.textContent = 'R';
                resetBtn.onclick = () => {
                    input.value = '';
                    updateWorkHours();
                };
                cell.appendChild(input);
                cell.appendChild(resetBtn);
            } else if (i === 3) { // 휴가 시간 선택
                let select = document.createElement('select');
                ['없음', '2시간', '4시간'].forEach(option => {
                    let optionElement = document.createElement('option');
                    optionElement.value = option;
                    optionElement.textContent = option;
                    select.appendChild(optionElement);
                });
                select.value = localStorage.getItem(`vacationTime${index + 1}`) || '없음';
                cell.appendChild(select);
            } else if (i === 4) { // 휴일 체크박스
                let label = document.createElement('label');
                label.className = 'custom-checkbox';

                let input = document.createElement('input');
                input.type = 'checkbox';
                input.checked = localStorage.getItem(`holiday${index + 1}`) === 'true';

                let span = document.createElement('span');

                label.appendChild(input);
                label.appendChild(span);
                cell.appendChild(label);
            } else {
                cell.innerText = ''; // 나머지 셀은 초기값 설정
            }
        }
    });
}

function updateWorkHours() {
    const { totalHolidayTime, totalAccumulatedMinutes, totalVacationMinutes } = calcTotalRequiredMinutesAndUpdateTable();
    updateChart(totalAccumulatedMinutes - totalVacationMinutes, remainingMinutes, totalHolidayTime, totalVacationMinutes);
}
// update chart
function updateChart(completedTime, remainingTime, holidayTime, vacationTime) {
    workTimeChart.data.datasets[0].data[0] = holidayTime;
    workTimeChart.data.datasets[0].data[1] = completedTime;
    workTimeChart.data.datasets[0].data[2] = vacationTime;
    workTimeChart.data.datasets[0].data[3] = remainingTime;
    workTimeChart.update();
    var numColor = color2;
    if (remainingMinutes > 0) {
        numColor = color2
    } else {
        numColor = color1
    }
    document.getElementById('total-remainning-time').innerHTML =
        '<div>잔여 근무시간' + '<span class="total-remainning-time-num" style="color: ' + numColor + '">' + formatMinutesAsHours(remainingMinutes) + '</span></div>'; // 잔여 근무 시간 업데이트

    document.getElementById('dayExitTime').innerText = '';
}

// 잔여 근무 시간 계산
function calcTotalRequiredMinutesAndUpdateTable() {
    const rows = document.getElementById('workHoursTable').rows;
    let totalAccumulatedMinutes = 0;

    // 휴일에 대해 빼줄 근무 시간 총합
    let totalHolidayTime = 0;
    let totalVacationMinutes = 0;

    for (let i = 1; i < rows.length; i++) {
        const isHoliday = rows[i].cells[4].children[0].children[0].checked;
        rows[i].cells[0].style.color = color3;
        rows[i].style.backgroundColor = color4;
        if (isHoliday) {
            // 휴일인 경우 전체 근무 시간에서 하루 8시간(480분)을 빼줌
            totalHolidayTime += 8 * 60;
            rows[i].cells[5].innerText = '휴일(8시간 제외)'; // 휴일인 경우 근무 시간을 '휴일'로 표시
            rows[i].cells[5].style.color = color6;
            rows[i].style.backgroundColor = color7;
            rows[i].cells[1].children[0].style.backgroundColor = color7;
            // rows[i].cells[1].children[0].value = '';
            rows[i].cells[1].children[0].disabled = true;
            rows[i].cells[2].children[0].style.backgroundColor = color7;
            // rows[i].cells[2].children[0].value = '';
            rows[i].cells[2].children[0].disabled = true;
            rows[i].cells[3].children[0].style.backgroundColor = color7;
            rows[i].cells[3].children[0].value = '없음';
            rows[i].cells[3].children[0].disabled = true;
            rows[i].cells[6].innerText = '';
            rows[i].cells[0].style.color = color6;
            // rows[i].cells[5].backgroundColor = color5;
            continue;
        }

        rows[i].cells[1].children[0].style.backgroundColor = color4;
        rows[i].cells[2].children[0].style.backgroundColor = color4;
        rows[i].cells[3].children[0].style.backgroundColor = color4;
        rows[i].cells[1].children[0].disabled = false;
        rows[i].cells[2].children[0].disabled = false;
        rows[i].cells[3].children[0].disabled = false;

        const startTime = rows[i].cells[1].children[0].value;
        const endTime = rows[i].cells[2].children[0].value;
        const vacationTime = rows[i].cells[3].children[0].value;
        let vacationMinutes = vacationTime === '없음' ? 0 : parseInt(vacationTime) * 60;
        totalVacationMinutes += vacationMinutes;
        let workMinutes = calculateWorkDuration(startTime, endTime);

        // 근무 인정 시간에 휴가 시간 포함
        workMinutes += vacationMinutes;
        let dailyMaxWorkMinutes = Math.min(workMinutes, 9 * 60); // 하루 최대 근무 인정 시간 9시간으로 제한

        totalAccumulatedMinutes += dailyMaxWorkMinutes; // 휴가 시간 포함하여 누적

        let hours = Math.floor(dailyMaxWorkMinutes / 60);
        let mins = dailyMaxWorkMinutes % 60;
        rows[i].cells[5].innerText = (workMinutes > 0) ? `${pad(hours)}:${pad(mins)}` : ''; // 근무 인정 시간 업데이트
        if (dailyMaxWorkMinutes < 480) {
            if (dailyMaxWorkMinutes === 0) {
                rows[i].cells[5].style.color = color3;
                // rows[i].cells[5].style.backgroundColor = color4
            } else {
                rows[i].cells[5].style.color = color2;
                if (dailyMaxWorkMinutes < 0) {
                    rows[i].cells[5].innerText = `출퇴근시간 AM/PM 확인`;
                }
            }
        } else {
            rows[i].cells[5].style.color = color1;
        }
        // 적립시간 표시
        rows[i].cells[6].innerText = ''; // 리셋먼저...
        if (i < rows.length && dailyMaxWorkMinutes !== 0) {
            let addedTime = dailyMaxWorkMinutes - 480;
            let isMinus = false;
            if (addedTime < 0) {
                isMinus = true;
                addedTime *= -1;
            }
            let _hours = Math.floor(addedTime / 60);
            let _mins = addedTime % 60;
            if (isMinus) {
                rows[i].cells[6].innerText = `-${pad(_hours)}:${pad(_mins)} 부족`;
                rows[i].cells[6].style.color = color2;
            } else {
                if (addedTime === 0) {
                    rows[i].cells[6].style.color = color3;
                    rows[i].cells[6].innerText = '.';
                } else {
                    rows[i].cells[6].innerText = `+${pad(_hours)}:${pad(_mins)} 적립`;
                    rows[i].cells[6].style.color = color1;
                }
            }
        }
    }
    let totalRequiredMinutes = (40 * 60) - totalHolidayTime; // 주당 근무 시간에서 휴일 시간을 뺀 값
    remainingMinutes = Math.max(0, totalRequiredMinutes - totalAccumulatedMinutes); // 음수 방지

    return { totalHolidayTime, totalAccumulatedMinutes, totalVacationMinutes };
}

function calculateWorkDuration(startTime, endTime) {
    const startMoment = moment(startTime, "HH:mm");
    const endMoment = moment(endTime, "HH:mm");
    let duration = 0;
    if (startMoment.isValid() && endMoment.isValid()) {
        duration = moment.duration(endMoment.diff(startMoment)).asMinutes();
        // 점심 시간 체크
        if (!endMoment.isBefore(moment('12:30', "HH:mm")) && !startMoment.isAfter(moment('13:30', "HH:mm"))) {
            duration -= 60;
        }
    }
    return duration;
}

function formatMinutesAsHours(minutes) {
    let hours = Math.floor(minutes / 60);
    let mins = minutes % 60;
    return `${pad(hours)}:${pad(mins)}`;
}

function pad(number) {
    return number < 10 ? '0' + number : number.toString();
}


function saveTimeToLocalStorage() {
    const rows = document.getElementById('workHoursTable').rows;
    for (let i = 1; i < rows.length; i++) {
        const startTime = rows[i].cells[1].children[0].value;
        const endTime = rows[i].cells[2].children[0].value;
        const vacationTime = rows[i].cells[3].children[0].value;
        const isHoliday = rows[i].cells[4].children[0].children[0].checked;

        localStorage.setItem(`startTime${i}`, startTime);
        localStorage.setItem(`endTime${i}`, endTime);
        localStorage.setItem(`vacationTime${i}`, vacationTime);
        localStorage.setItem(`holiday${i}`, isHoliday);
    }
}

function resetAll() {
    const rows = document.getElementById('workHoursTable').rows;
    for (let i = 1; i < rows.length; i++) {
        localStorage.removeItem(`startTime${i}`);
        localStorage.removeItem(`endTime${i}`);
        localStorage.removeItem(`vacationTime${i}`);
        localStorage.removeItem(`holiday${i}`);
        rows[i].cells[0].style.color = color3;
        rows[i].cells[1].children[0].value = '';
        rows[i].cells[2].children[0].value = '';
        rows[i].cells[3].children[0].value = '없음';
        rows[i].cells[4].children[0].children[0].checked = false;
        rows[i].cells[5].innerText = '';
        // rows[i].cells[5].style.backgroundColor = color4;
        rows[i].style.backgroundColor = color4;
        rows[i].cells[5].style.color = color3;
        rows[i].cells[6].innerText = '';
        rows[i].cells[1].children[0].style.backgroundColor = color4;
        rows[i].cells[2].children[0].style.backgroundColor = color4;
        rows[i].cells[3].children[0].style.backgroundColor = color4;
        rows[i].cells[1].children[0].disabled = false;
        rows[i].cells[2].children[0].disabled = false;
        rows[i].cells[3].children[0].disabled = false;
    }
    remainingMinutes = 2400;
    document.getElementById('total-remainning-time').innerHTML = '<div>잔여 근무시간' + '<span class="total-remainning-time-num" style="color: ' + color2 + '">' + formatMinutesAsHours(remainingMinutes) + '</span></div>';
    document.getElementById('dayExitTime').innerText = '';
    updateChart(0, 2400);
}

function calculatedayExitTime() {
    var targetDayOfWeek = "";
    const rows = document.getElementById('workHoursTable').rows;
    let targetRow = null;

    for (let i = 1; i < rows.length; i++) {
        if (
            rows[i].cells[2].children[0].value === '' &&
            rows[i].cells[4].children[0].children[0].checked === false) {
            targetRow = rows[i];
            if (i === 1) {
                targetDayOfWeek = "월요일";
            } else if (i === 2) {
                targetDayOfWeek = "화요일";
            } else if (i === 3) {
                targetDayOfWeek = "수요일";
            } else if (i === 4) {
                targetDayOfWeek = "목요일";
            } else if (i === 5) {
                targetDayOfWeek = "금요일";
            }
            break;
        }
    }
    if (targetRow === null) {
        alert(`퇴근시간이 궁금한 요일의 퇴근시간을 비워주세요.`);
        return;
    }
    const startTime = targetRow.cells[1].children[0].value; // 출근 시간

    if (!startTime) {
        alert(`${targetDayOfWeek}의 출근 시간을 입력해주세요.`);
        return;
    }

    let remainingTotalMinutes = remainingMinutes;// (remainingHours * 60) + remainingMinutes;

    const targetdayStartMoment = moment(startTime, "HH:mm");
    const lunchStart = moment('12:30', "HH:mm");
    const lunchEnd = moment('13:30', "HH:mm");

    // 출근 시간과 남은 근무 시간을 기준으로 초기 퇴근 시간을 계산
    let tentativeTargetdayExitMoment = targetdayStartMoment.clone().add(remainingTotalMinutes, 'minutes');

    let isLaunchTime = false;
    // 점심시간이 근무 시간에 포함되어 있는지 확인 후 조정
    if (targetdayStartMoment.isBefore(lunchEnd) && tentativeTargetdayExitMoment.isAfter(lunchStart)) {
        // 점심시간이 포함되어 있으면, 퇴근 시간을 60분 연장
        remainingTotalMinutes += 60;
        isLaunchTime = true;
    }
    // 금요일이 아닌경우 휴가가 포함되어있으면 휴가시간 제외
    if (targetDayOfWeek !== "금요일") {
        const vacationTime = targetRow.cells[3].children[0].value;
        let vacationMinutes = vacationTime === '없음' ? 0 : parseInt(vacationTime) * 60;
        remainingTotalMinutes -= vacationMinutes;
    }

    // 조정된 근무 시간으로 최종 퇴근 시간 계산
    const targetdayExitMoment = targetdayStartMoment.clone().add(remainingTotalMinutes, 'minutes');

    // 퇴근 시간을 AM/PM 포맷으로 출력
    const exitTimeFormatted = targetdayExitMoment.format("hh:mm A");
    let remainingTimeFormatted = formatMinutesAsHours(remainingTotalMinutes);

    const vacationTime = targetRow.cells[3].children[0].value;
    let checkVacationMinutes = vacationTime === '없음' ? 0 : parseInt(vacationTime) * 60;
    let checkRemainingTotalMinutes = remainingTotalMinutes + checkVacationMinutes;
    if (checkRemainingTotalMinutes / 60 > 10) {
        let overWorkTime = checkRemainingTotalMinutes - 540 - 60;
        let overWorkTimeFormatted = formatMinutesAsHours(overWorkTime);
        const targetdayOverExitMoment = targetdayStartMoment.clone().add(remainingTotalMinutes - overWorkTime, 'minutes');
        const exitOverTimeFormatted = targetdayOverExitMoment.format("hh:mm A");
        document.getElementById('dayExitTime').innerHTML = `<span class="dayExitTimeNormal">${targetDayOfWeek} 근무시간을 최대한 채울 수 있는 시간(9시간)인 </span>${exitOverTimeFormatted}<span class="dayExitTimeNormal">에 퇴근하면</span><br /> 
            <span class="dayExitTimeNormal">남은 총 근무시간은</span> <span class="dayExitTimeAlert">${overWorkTimeFormatted}</span> <span class="dayExitTimeNormal">입니다.</span>`
    } else {
        document.getElementById('dayExitTime').innerHTML
            = `<span class="dayExitTimeNormal">남은 근무시간은 ${isLaunchTime ? "휴게시간 포함</span>" : "</span>"} ${remainingTimeFormatted} <br />
                <span class="dayExitTimeNormal">${targetDayOfWeek} 퇴근은</span> ${exitTimeFormatted} <span class="dayExitTimeNormal">이후부터 가능해요.</span>`;
    }
}

document.getElementById('workHoursTable').addEventListener('change', (event) => {
    updateWorkHours();
    saveTimeToLocalStorage();
});

 

 

style.css

html,
body {
    margin: 20px;
    padding: 0px;
    min-height: calc(100%-40px);
    font-family: 'Pretendard Variable', 'Apple SD Gothic Neo', sans-serif;
    font-size: 16px;
    font-weight: 400;
    line-height: 1.5;
    color: #666666;
    text-size-adjust: 100%;
    overflow-x: hidden;
    overflow-y: auto;
    text-align: center;
}

table {
    width: 100%;
    border-collapse: collapse;
    table-layout: fixed;
}

table,
th,
td {
    border: 1px solid #dfdfdf;
}

th,
td {
    padding: 10px;
    text-align: center;
    word-wrap: break-word;
}

.table-title {
    background-color: #f3f3f3;
    width: auto;
}

.first {
    width: 40px;
}

.second {
    width: 100px;
}

.third {
    width: 100px;
}

.forth {
    width: 60px;
}

.fifth {
    width: 50px;
}

.sixth {
    width: 180px;
}

.seventh {
    width: 150px;
}

.chart-container {
    position: relative;
    text-align: center;
    display: flex;
    justify-content: center;
    align-items: center;
    /* height: 100vh; */
}

canvas {
    position: relative;
    max-width: auto;
    max-height: 300px;
    margin-bottom: 40px;
    z-index: 100;
}

.total-remainning-time {
    position: absolute;
    width: 100%;
    height: 100%;
    margin: 0px;
    padding: 0px;
    z-index: 0;
    top: -40px;
    left: 0px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 12px;
    pointer-events: none;
}

.total-remainning-time-num {
    font-weight: 900;
    display: block;
    font-size: 20px;
}

button {
    margin: 10px 5px;
    padding: 10px 20px;
    font-size: 1rem;
    cursor: pointer;
    border: none;
    background-color: #0ACFD9;
    color: #ffffff;
    border-radius: 10px;
    transition: background-color 0.3s ease;
}

.reset-all {
    background-color: #898989;
}

.item-reset {
    position: relative;
    font-size: 0.7rem;
    width: 20px;
    height: 20px;
    text-align: center;
    border-radius: 10px;
    margin: 0px;
    padding: 0px;
    background-color: #d2d2d2;
}

.table-container {
    width: 100%;
    overflow-x: auto;
}

.action-container {
    text-align: center;
    margin-top: 40px;
    margin-bottom: 40px;
}

button:hover {
    background-color: #18aab1;
}

.item-reset:hover,
.reset-all:hover {
    background-color: #7d7d7d;
}

#dayExitTime {
    margin-top: 20px;
    font-size: 14px;
    font-weight: 400;
    color: #0ACFD9;
}

.dayExitTimeNormal {
    color: #6f6f6f;
    font-weight: 200;
}

.dayExitTimeAlert {
    color: #EF6D81;
    font-weight: 400;
}

.title-center {
    font-weight: 900;
    font-size: 3rem;
    text-align: center;
    margin-top: 20px;
    margin-bottom: 20px;
    color: #3f3f3f;
}

input[type="text"] {
    padding: 5px 5px 5px 5px;
    border-radius: 5px;
    border: 1px solid #d4d4d4;
    color: #777777;
    background-color: #ffffff;
    width: 80px;
    margin-right: 10px;
}

select {
    padding: 5px 5px 5px 5px;
    border-radius: 5px;
    border: 1px solid #d4d4d4;
    color: #777777;
    background-color: #ffffff;
    text-align: center;
}

.custom-checkbox input {
    display: none;
}

/* 커스텀 디자인 */
.custom-checkbox span {
    height: 18px;
    width: 18px;
    background-color: #ffffff;
    display: inline-block;
    position: relative;
    border-radius: 5px;
    border: 1px solid #d4d4d4;
}

/* 체크된 상태의 스타일 */
.custom-checkbox input:checked+span {
    background-color: #DCBCFF;
    border: 1px solid #B068FF;
}

.custom-checkbox input:checked+span:after {
    content: "";
    position: absolute;
    left: 7px;
    top: 3px;
    width: 3px;
    height: 8px;
    border: solid white;
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

@media screen and (max-width: 1100px) {

    html,
    body {
        margin: 10px;
        font-size: 14px;
        line-height: 1.3;
        min-height: calc(100%-20px);
    }

    .title-center {
        font-size: 2.5rem;
        text-align: center;
        margin-top: 0px;
        margin-bottom: 20px;
    }

    th,
    td {
        padding: 10px 0px 10px 0px;
    }


}

@media screen and (max-width: 935px) {


    th,
    td {
        padding: 10px 0px 10px 0px;
    }

    input[type="text"],
    select {
        width: 60px;
        transform: scale(0.9);
    }

    input[type="checkbox"] {
        transform: scale(1.3);
    }
}

@media screen and (max-width: 784px) {

    table,
    thead,
    tbody,
    th,
    td,
    tr {
        display: block;
        width: 100%;
        border: 0px;
    }

    .title-center {
        font-size: 2.5rem;
        text-align: center;
        margin-top: 0px;
        margin-bottom: 20px;
    }

    thead tr {
        position: absolute;
        top: -9999px;
        left: -9999px;
        border: 0px;
    }

    tr {
        border: 1px solid #ccc;
        border-radius: 0px;
        margin-bottom: 20px;
        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
        width: calc(100% - 2px);
    }

    td {
        position: relative;
        align-items: flex-start;
        justify-content: center;
        border: none;
        border-bottom: 1px solid #dedede;
        /* padding: 10px; */
        padding: 10px 10px 10px 10px;
        padding-left: calc(28% + 30px);
        text-align: right;
        white-space: normal;
        height: 22px;
        width: auto;
        margin: 0px;
    }

    td:last-child {
        border-bottom: none;
    }

    td:before {
        position: absolute;
        top: 0;
        left: 0;
        width: 28%;
        height: 22px;
        padding: 10px;
        content: attr(data-title);
        font-weight: bold;
        font-size: 14px;
        color: #666666;
        background-color: #f3f3f3;
        text-align: right;
    }

    input[type="text"],
    select {
        margin: 0px;
        padding: 4Px;
        transform: scale(1);
        margin-right: 10px;
        width: auto;
        /* max-width: 100%; */
    }

    select {
        transform: scale(1);
        /* padding: 5px 5px 5px 5px; */
        border-radius: 5px;
        border: 1px solid #d4d4d4;
        color: #777777;
        background-color: #ffffff;
        margin: 0px;
        padding: 4Px;
        /* text-align: center; */
        /* margin: 0px; */
    }

    input[type="checkbox"] {
        transform: scale(1.3);
        margin: 0px;
        /* margin-right: 5px; */
        color: #777777;
        margin: 0px;
        padding: 4Px;
    }
}

@media screen and (max-width: 510px) {

    html,
    body {
        margin: 10px;
        font-size: 14px;
        min-height: calc(100%-20px);
    }

    .title-center {
        font-size: 2rem;
        text-align: center;
    }

    .action-container,
    .title-center,
    #dayExitTime {
        margin-top: 15px;
        margin-bottom: 15px;
    }

    td {
        font-size: 12px;
        padding-left: calc(40% + 30px);
        height: 18px;
    }

    td:before {
        width: 40%;
        font-size: 12px;
        height: 18px;
    }

    input[type="text"],
    select {
        max-width: 60%;
        padding: 4px;
        font-size: 12px;
    }

    button {
        font-size: 0.8rem;
        font-size: 14px;
    }

    .item-reset {
        padding: 0px 0px;
        font-size: 0.8rem;
        font-size: 12px;
    }
}

@media screen and (max-width: 309px) {
    .total-remainning-time {
        top: -50px;
        font-size: 10px;
    }

    .total-remainning-time-num {
        font-size: 18px;
    }
}
반응형
반응형

매번 까먹는 출근도장을 자동으로 찍을 수 있게 만들어보자.

방법은 파이선을 사용해 웹페이지 크롤링 하는 방식으로 진행하였다.

 

 

Slenium 및 WebDriver설치

 

터미널에

pip3 install selenium

 

autoLogin.py

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
import time

# 첫 번째와 두 번째 커맨드 라인 인자를 읽습니다.
# username = sys.argv[1]
# password = sys.argv[2]

driver = webdriver.Safari()
driver.get('https://flex.team/home')  # 웹페이지를 엽니다.

# 입력 필드가 로딩될 때까지 기다립니다.
email_field = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, 'radix-:r0:'))
)

# 입력 필드에 텍스트를 입력합니다.
email_field.send_keys('플렉스개인이메일아이디' + Keys.RETURN)

# 비밀번호 입력 필드가 로딩될 때까지 기다립니다.
password_field = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, 'radix-:rf:'))
)

# 비밀번호 입력 필드에 텍스트를 입력합니다.
password_field.send_keys('플렉스비밀번호' + Keys.RETURN)

time.sleep(10)

# ?? 사이드바 펼치기
# c-bIRrzL c-bIRrzL-dcxSjI-size-default c-bIRrzL-lbEVww-variant-ghost c-bIRrzL-ibjCwas-css
# c-bIRrzL c-bIRrzL-dcxSjI-size-default c-bIRrzL-lbEVww-variant-ghost c-bIRrzL-ibjCwas-css
button0 = WebDriverWait(driver, 20).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, '.c-bIRrzL.c-bIRrzL-dcxSjI-size-default.c-bIRrzL-lbEVww-variant-ghost.c-bIRrzL-ibjCwas-css'))
)
button0.click()

time.sleep(3)

# ?? 근무시작버튼 클릭
button1 = WebDriverWait(driver, 20).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, '.c-bIRrzL.c-gNtnBi.c-bIRrzL-glaMVD-size-default.c-bIRrzL-duTMEK-variant-outline.c-bIRrzL-icJWhYr-css.c-toRGo'))
)
button1.click()

time.sleep(3)

# ?? 사무실근무 버튼 클릭
button2 = WebDriverWait(driver, 20).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, '.c-eECIIA'))
)
button2.click()

time.sleep(3)

# ?? 근무 시작 클릭
button3 = WebDriverWait(driver, 20).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, '.c-eTLAUT'))
)
button3.click()

time.sleep(3)

driver.quit()

 

단독 실행파일로 만들기

pyinstaller 설치

터미널에

pip3 install pyinstaller

 

pyinstaller설치후에 zsh문제로 설치가 되어있지 않다고 한다면

open -e ~/.zshrc

 

아래 줄 추가 후 파일 저장 (이부분은 설치위치에 따라 달라짐)

export PATH="/Users/Name/Library/Python/3.9/bin:$PATH"

 

터미널을 닫고 다시 열어

source ~/.zshrc

 

버전확인

pyinstaller --version

 

단독파일로 만드는 명령어 (뒤에 파일 경로를 맞춰줘야 할 수도 있음)

sudo pyinstaller --onefile --windowed autoLogin.py

변환된 애플리케이션은 /Users/Name/Library/Python/3.9/lib/python/site-packages/my_script/dist/ 폴더 안에 생성

 

 

컴터켤때 실행파일이 자동으로 실행되게 하기

 

System settings > General > Login Items 로 진입

Open at Login 해당 추가

반응형
반응형

[과정요약]

1. 필요한 도구 설치

2. GLB를 GLTF로 변환(Separate)

3. 이미지 압축

4. GLTF파일에 Draco 압축 적용

5. Draco 압축이 적용된 GLTF를 다시 GLB로 변환


1. 필요한 도구 설치

먼저, GLTF 및 Draco 관련 작업을 수행할 수 있는 도구를 설치해야 합니다. 이 예제에서는 gltf-pipeline을 사용하겠습니다. Node.js를 설치한 후, 다음 명령을 실행하여 gltf-pipeline 및 Draco 라이브러리를 설치합니다.

npm install --global gltf-pipeline @google/draco3d

 

2. GLB를 GLTF로 변환(Separate)

Terminal을 열고 GLB가 있는 폴더로 이동합니다. 해당 경로를 쉽게 할기 위한 방법으로 Finder를 켜고 메뉴에서 View > Show Path Bar를 클릭하면 Finder하단애 경로가 나타납니다.

경로의 마지막 폴더이름에 마우스를 가져가 우클릭하여 "Copy 폴더이름 as Pathname"을 클릭하면 경로가 복사됩니다.

터미널에 아래와 같이 "cd"를 입력, 한칸띄고 복사한 경로를 붙여넣어 줍니다.

GLB 파일을 GLTF로 변환하려면 다음 명령을 실행합니다. 여기서 "input.glb"는 원본 GLB 파일의 이름이고 "temp.gltf"는 변환된 GLTF 파일의 이름입니다.

gltf-pipeline -i input.glb -o temp.gltf --separate

 

명령어를 실행하면 아래와 같이 포함되어있던 이미지 파일들이 나오고 변환 된 GLTF파일도 보이게 됩니다.

 

3. 이미지 압축

Squoosh사이트를 열고 이미지들을 하나씩 압축합니다. 이때 투명도가 꼭 필요 한 파일은 png로 유지되도록 뽑아주고 필요하지 않는 파일은 jpg로 뽑아주면 압축율이 올라갑니다. 오른쪽하단에 다운로드 버튼을 클릭하여 새로 받아진 이미지 파일을 원본 이미지 파일로 바꿔줍니다.

만약 png파일이었던 이미지가 jpg형태로 바뀌게 되었다면 GLTF파일을 텍스트 에디터로 열어 해당 파일의 확장명을 변경해줘야 합니다.

이 예제에서는 아래 이미지가 png였는데 jpg로 바뀌게되어 해당 파일의 확장명을 바꿔주었습니다.

temp.gltf파일을 마우스 우클릭하여 Open With > Other...을 클릭합니다.

Applications > TextEdit.app으로 열어줍니다.

Command + F를 눌러 해당 파일을 검색해줍니다. 

mimeType과 uri에 기존 png로 되어있던 부분을 각각 jpeg / jpg로 바꿔줍니다.

해당 되는 나머지 파일도 모두 바꿔준 후 파일을 저장하고 나옵니다.

 

 

4. GLTF파일에 Draco 압축 적용

압축하려는 GLTF 파일에 Draco 압축을 적용하려면 다음 명령을 실행합니다. 여기서 "temp.gltf"는 변환된 GLTF 파일의 이름이고 "compressed.gltf"는 Draco 압축이 적용된 GLTF 파일의 이름입니다.

gltf-pipeline -i temp.gltf -o compressed.gltf --draco.compressionLevel 10

 

 

5. Draco 압축이 적용된 GLTF를 다시 GLB로 변환

압축된 GLTF 파일을 다시 GLB로 변환하려면 다음 명령을 실행합니다. 여기서 "compressed.gltf"는 Draco 압축이 적용된 GLTF 파일의 이름이고 "output.glb"는 최종 GLB 파일의 이름입니다.

gltf-pipeline -i compressed.gltf -o output.glb

 

 

웹 브라우저에 gltf.report로 이동 후 해당 GLB파일이 잘 열리는지 확인해 봅니다.

 

예제에서 사용했던 원본 GLB의 용량은 19.7MB였고 압축과정을 마친 후의 용량은 6.2MB로 줄었습니다.

 

 

참조: How to Compress .GLB File size (12MB to 2MB) - YouTube

반응형

'tip' 카테고리의 다른 글

Unity Visual Studio Code 연결 mac  (0) 2015.06.23
티스토리 블로그에 SyntaxHighlighter 3.0 적용하기  (0) 2015.02.10
반응형

프리다이빙을 위한 장비

프리다이빙은 안전과 편안함을 보장하기 위해 여러 장비가 필요합니다. 다음은 프리 다이빙을 할 때 고려해야 할 몇 가지 필수 장비입니다.

- 웻슈트: 잠수복은 찬물에서 몸을 따뜻하게 유지하는 데 도움이 됩니다.
- 마스크: 수중을 보려면 마스크가 필요합니다.

 
- 스노클: 스노클을 사용하면 물에서 머리를 떼지 않고도 수면에서 숨을 쉴 수 있습니다.
- 롱핀: 롱핀은 수중에서 보다 효율적으로 수영하는 데 도움이 됩니다.
- 웨이트 벨트: 웨이트 벨트는 수중에서 중성 부력을 얻는 데 도움이 됩니다.
- 다이브 컴퓨터: 다이브 컴퓨터는 잠수 깊이와 시간, 감압 상태를 모니터링하는 데 도움이 됩니다.
- 기타 안전 장비: 다이브 나이프, 수면 마커 부표, 다이빙 휘슬과 같은 안전 장비는 비상 상황에서 유용할 수 있습니다.


이 모든 장비는 고품질이어야 하고 제대로 작동해야 한다는 점에 유의하는 것이 중요합니다. 매번 다이빙을 하기 전에 장비를 점검하고 적절하게 유지 관리되고 작동하는지 확인하는 것이 중요합니다. 또한 안전하고 즐거운 다이빙을 위해 항상 파트너와 함께 다이빙하고 적절한 안전 프로토콜을 따르는 것이 가장 좋습니다.

 

 

1. 웻슈트

프리 다이버들 사이에서 인기 있는 여러 브랜드가 있습니다. 다음은 그중 몇 가지입니다.

  1. O'Neill Wetsuits
  2. Xcel Wetsuits
  3. Cressi Wetsuits
  4. Patagonia Wetsuits
  5. Rip Curl Wetsuits
  6. ...

당신에게 가장 적합한 잠수복은 개인의 필요와 선호도에 따라 달라진다는 점에 유의하는 것이 중요합니다. 잠수복을 선택할 때 수온, 두께 및 내구성과 같은 요소를 고려하십시오. 잠수복이 편안하게 맞고 적절한 따뜻함과 이동성을 제공하는지 확인하기 위해 구매하기 전에 여러 개의 잠수복을 입어보는 것도 좋은 생각입니다.

 

 

2. 마스크

프리다이빙을 위한 마스크를 선택할 때 고려해야 할 몇 가지 주요 기능이 있습니다. 다음은 가장 중요한 몇 가지 사항입니다.

- 착용감: 

마스크는 누수 없이 편안하고 안전하게 얼굴에 맞아야 합니다. 잘 맞으면 마스크로 물이 새지 않아 다이빙하는 동안 불편하고 주의가 산만해질 수 있습니다.


- 렌즈: 

렌즈는 내구성이 강하고 긁힘에 강한 강화 유리로 만들어져야 합니다. 렌즈는 또한 항력을 줄이고 마스크가 부력에 미치는 영향을 최소화하기 위해 로우 프로파일 디자인이어야 합니다.


- 스커트: 

마스크의 스커트는 부드럽고 유연한 소재로 만들어져 얼굴 주위를 잘 밀봉해야 합니다. 실리콘 스커트는 내구성이 뛰어나고 편안하며 세척이 쉽기 때문에 인기 있는 선택입니다.

 

프리다이빙 마스크의 인기 브랜드는 다음과 같습니다.

  1. Cressi
  2. Mares
  3. Oceanic
  4. Tusa
  5. Hollis
  6. ...

편안하게 맞고 밀착력이 좋은 마스크를 찾기 위해 구매하기 전에 여러 마스크를 착용해 보는 것이 중요합니다. 일부 다이브 샵에서는 수영장에서 마스크를 착용해 볼 수 있는 기회를 제공합니다. 이는 구매하기 전에 적합성을 테스트할 수 있는 좋은 방법이 될 수 있습니다.

 

 

3. 스노클

스노클과 관련하여 선택할 수 있는 브랜드와 모델이 많으며 개인의 필요와 선호도에 따라 적합한 제품을 선택할 수 있습니다. 다음은 고려해 볼 수 있는 몇 가지 인기 있는 스노클 브랜드입니다.

- Cressi: 

Cressi는 80년 이상 다이빙 및 스노클링 산업에 종사해 온 이탈리아 브랜드입니다. 그들은 모든 수준의 경험과 기술을 위한 다양한 고품질 스노클을 제공합니다.


- Mares: 

Mares는 모든 수준의 다이빙과 스노클링을 위한 다양한 스노클을 제공하는 잘 알려진 브랜드입니다. 그들의 스노클은 편안함, 내구성 및 성능을 위해 설계되었습니다.


- Aqua Lung: 

Aqua Lung은 모든 유형의 다이버와 스노클러의 요구를 충족하는 다양한 스노클을 제공하는 인기 브랜드입니다. 그들의 스노클은 편안함과 품질로 유명합니다.


- TUSA: 

TUSA는 다이빙 및 스노클링 업계에서 존경받는 브랜드로 모든 수준의 경험과 기술 요구를 충족하는 다양한 스노클을 제공합니다. 그들의 스노클은 내구성과 편안함으로 유명합니다.


스노클을 선택할 때 물의 유형, 기술 및 경험 수준, 고르지 못한 물을 위한 드라이 탑 스노클과 같은 특별한 요구 사항과 같은 요소를 고려하십시오. 또한 스노클이 편안하고 안전하게 잘 맞는지, 부식과 자외선에 강한 내구성 있는 소재로 만들어졌는지 확인하세요.

 

 

4. 롱핀

프리다이빙 롱핀은 크게 고무, 플라스틱, 파이버 글라스, 카본 4가지 종류의 소재를 사용합니다. 대표적인 브랜드는 다음과 같습니다.

1. 리더스

2. 알케미

3. 몰차노브

4. 세트마

5. 디플리

 

각 제품중 카본제품위주로 설명하자면

 

- 리더스는

입문 및 상급자들도 많이 사용하는 브랜드로 카본제품도 20만원대부터 시작하여 많은 다이버들이 사용하는 브랜드입니다.

 

- 알케미는

흔히 말하는 '낭창거리다'라는 표현의 원조급 제품으로 가볍고 탄성이 좋은 제품이며, 다이버들 사이에서 인지도가 높은 브랜드입니다. 가격대는 70~100만원 정도 합니다.

 

- 몰차노바는

프리다이빙 세계 1위 기록 보유자 알렉세이 몰차노바의 브랜드로, 발에 딱 맞는 맞춤 풋포켓을 제작 할 수 있는 브랜드로 유명합니다. 가격대는 85만원 부터 입니다.

 

- 세트마는

롱핀 중에서도 상당히 길이가 긴 모델이 나오며, 타사 제품에 비해 긴 보증 기간과 나사 방식 채결 결합을 선택할 수 있습니다. 가격대는 50~80만원 정도 합니다.

 

- 디플리는 

최근 초신성처럼 뜨고 있는 국내 브랜드로 그동안 국내 다이버들 사이에 유명했던 해외 제품들과 비교해 탄선, 강도 무엇 하나 빠지는 것이 없이 좋은 제품이고 각격대는 50~70만원 정도로 형성되어 있습니다.

 

출처 : https://m.blog.naver.com/lifetripper/221620256040

 

 

5. 웨이트 벨트

웨이트 벨트의 경우 선택할 수 있는 브랜드와 옵션이 많습니다. 인기 있는 브랜드로는 ScubaPro, Mares 및 Aqua Lung이 있습니다.

웨이트 벨트를 선택할 때 편안함, 내구성 및 조정 가능성과 같은 요소를 고려하십시오. 웨이트 벨트가 사용하는 버클 시스템의 유형과 벨트와 호환되는 웨이트의 크기와 무게도 고려해야 합니다.

웨이트 벨트는 적절한 감독 하에서만 사용해야 하며 몸에 잘 맞는 잠수복 및 기타 필요한 장비와 함께 사용해야 한다는 점을 명심하는 것이 중요합니다. 웨이트 벨트를 적절하고 안전하게 사용하는 방법을 아는 것도 중요합니다. 웨이트 벨트를 사용하기 전에 적절한 다이빙 기술을 숙지하고 항상 안전 지침과 규정을 따르십시오.

 

 

6. 다이빙 컴퓨터

다이빙의 경우 다이브 컴퓨터는 다이빙 데이터를 모니터링하고 안전한 다이빙 관행을 보장하는 중요한 장비가 될 수 있습니다. 다양한 다이브 컴퓨터를 제공하는 여러 브랜드가 있으며 각 브랜드에는 고유한 기능 세트가 있습니다. 다이브 컴퓨터 시장에서 가장 유명하고 잘 알려진 브랜드는 다음과 같습니다.

  1. Suunto
  2. Oceanic
  3. Mares
  4. Garmin
  5. Cressi
  6. Apple Watch Ultra 👍


다이브 컴퓨터를 선택할 때 자신의 다이빙 요구 사항과 선호 사항은 물론 수행할 다이빙 유형을 고려하는 것이 중요합니다. 결정을 내릴 때 컴퓨터의 크기와 무게, 배터리 수명, 내수성 및 사용 용이성과 같은 요소를 고려할 수 있습니다. 또한 많은 다이브 컴퓨터는 이제 무선 연결과 다이브 로그를 저장 및 공유할 수 있는 기능을 제공하며, 이는 일부 다이버에게 유용한 기능이 될 수 있습니다.

 

 

7. 기타 안정장비

프리다이빙의 경우 안전하고 즐거운 경험을 위해 적절한 안전 장비를 갖추는 것이 중요합니다. 프리 다이빙에 권장되는 안전 장비는 다음과 같습니다.

- 잠수 깃발: 다이버가 아래에 있음을 알리고 보트에게 멀리 떨어지라고 경고하기 위해 물 표면에 부착하는 밝고 화려한 깃발입니다.

- 비상 신호 장치: 호루라기 또는 표면 마커 부표는 비상 시 도움을 요청하는 신호로 사용할 수 있습니다.
- 백업 다이브 컴퓨터 또는 수심 게이지: 백업 다이브 컴퓨터 또는 수심 게이지는 기본 다이브 컴퓨터가 고장난 경우 중요한 정보를 제공할 수 있습니다.
- 표면 마커 부이(SMB): SMB는 해당 지역의 표면과 보트에 위치를 알리는 데 사용할 수 있습니다.
- 다이브 나이프: 다이브 나이프는 얽힌 선을 잘라내거나 도움을 청하는 데 사용할 수 있습니다.
- 응급처치 키트: 작은 응급처치 키트는 반창고, 방부제, 진통제와 같은 기본 품목을 포함하여 매 다이빙마다 휴대해야 합니다.


다음은 프리다이빙을 위한 권장 안전 장비 중 일부입니다. 특정 요구 사항과 다이빙 조건에 가장 적합한 안전 장비를 결정하려면 경험이 풍부한 프리 다이버 및 다이빙 전문가와 조사하고 상담하는 것이 중요합니다.

반응형

'freedive' 카테고리의 다른 글

프리다이빙이란?  (0) 2023.02.07
반응형

프리다이빙이란?

프리 다이빙은 스쿠버 장비나 다른 호흡 장치를 사용하지 않고 수행되는 수중 다이빙의 한 형태입니다. 대신 다이버는 가능한 한 오랫동안 수중에 머물기 위해 숨을 참는 것에 의존합니다. 프리 다이빙은 민물 호수와 강, 해수 바다, 심지어 수영장까지 다양한 환경에서 수행할 수 있습니다.

프리 다이빙은 레크리에이션 활동이자 경쟁 스포츠가 될 수 있습니다. 경쟁적인 프리 다이빙에서 다이버는 가능한 한 가장 깊은 수심에 도달하거나 가장 오랜 시간 동안 물속에 머무르거나 또는 두 가지 모두를 목표로 합니다. 다이버가 움직이지 않고 가능한 한 오랫동안 숨을 참는 정적 무호흡증과 다이버가 수면 위로 떠오르지 않고 가능한 한 오랫동안 수중에서 수영하는 동적 무호흡증을 포함하여 다양한 유형의 프리 다이빙도 있습니다.

프리 다이빙은 보람 있고 신나는 활동이 될 수 있지만, 시도하기 전에 안전 예방 조치를 취하고 적절한 교육을 받는 것이 중요합니다. 여기에는 적절한 호흡 및 이완 기술을 배우는 것뿐만 아니라 수중 환경에 대한 신체 반응을 적절하게 제어하고 모니터링하는 방법이 포함될 수 있습니다.

 

프리다이빙은 다이빙의 기본 원리에 대한 깊은 이해뿐만 아니라 우수한 수준의 체력과 정신적 준비가 필요한 스포츠입니다. 다음은 프리 다이빙과 관련된 몇 가지 주요 개념과 기술입니다.

호흡 기술: 다이버는 수중에서 오랫동안 숨을 참을 수 있어야 하므로 프리 다이빙에서 적절한 호흡은 매우 중요합니다. 여기에는 일반적으로 심호흡을 하고 완전히 숨을 내쉰 다음 잠수하기 전에 다시 심호흡을 하는 것이 포함됩니다.

 

휴식 기술: 수중에서 휴식을 취하는 능력 또한 프리 다이빙의 중요한 측면입니다. 여기에는 시각화, 호흡 운동 및 점진적 근육 이완과 같은 기술이 포함될 수 있습니다.

 

압력 평형: 다이버가 수중으로 더 깊이 내려갈수록 주변의 압력이 증가합니다. 이것은 귀와 부비강에 불편함을 유발할 수 있으므로 다이버가 압력을 균등화하는 방법을 배우는 것이 중요합니다. 이것은 일반적으로 코를 꼬집고 부드럽게 불어서 수행하여 유스타키오 관을 열고 압력을 완화하는 데 도움이 됩니다.

 

부력 조절: 좋은 부력 조절을 유지하는 것은 많은 에너지를 소비하지 않고 효과적으로 잠수할 수 있는 열쇠입니다. 다이버는 하강 및 상승 속도를 제어하기 위해 수중 위치를 조정할 수 있어야 합니다.

 

안전 조치: 프리다이빙은 적절한 안전 조치를 취하지 않으면 위험할 수 있습니다. 다이버가 관련된 위험을 이해하고 버디와 함께 다이빙하고 다이빙 플래그 및 수면 마커 부표와 같은 적절한 안전 장비를 사용하는 것과 같은 안전한 다이빙 관행을 따르는 것이 중요합니다.

 

이러한 기본 원칙 외에도 프리 다이버는 보트 다이빙, 수중 탐색, 해류 및 파도 대처와 같은 추가 기술을 배워야 할 수도 있습니다. 프리 다이빙을 시작하려면 자격을 갖춘 강사와 함께 초보자 코스를 수강하는 것이 좋습니다. 강사는 지도를 제공하고 기술을 쌓는 데 도움을 줄 수 있습니다.

 

 

1. 호흡 기술

호흡 기술은 프리 다이버에게 휴식의 중요한 측면입니다. 통제된 호흡은 몸을 진정시키고 스트레스를 줄이는 데 도움이 될 수 있으므로 산소를 더 쉽게 보존하고 수중 공황이나 불안을 피할 수 있습니다.

프리 다이버가 사용하는 일반적인 호흡 기술 중 하나는 "사각형 호흡"입니다. 정방형 호흡은 느리고 깊게 숨을 들이쉬고, 정해진 시간 동안 참았다가 천천히 내쉬고, 정해진 시간 동안 숨을 참은 다음 주기를 반복하는 것입니다. 이러한 유형의 호흡은 심장 박동을 늦추고 이완을 촉진하는 데 도움이 됩니다.

또 다른 대중적인 호흡 기술은 "이완된 횡격막 호흡"입니다. 이 기술은 가슴이 아닌 횡격막의 호흡에 초점을 맞추는 것과 관련이 있습니다. 편안한 횡격막 호흡을 연습하려면 등을 대고 누워 한 손은 가슴에, 다른 한 손은 배에 댑니다. 숨을 들이쉴 때 가슴보다는 배를 팽창시키는 데 집중하십시오. 천천히 그리고 완전히 숨을 내쉬십시오. 이것을 여러 번 반복하면 몸을 진정시키고 스트레스를 줄이는 데 도움이 됩니다.

프리 다이빙을 하는 동안 호흡에 주의를 기울이는 것도 중요합니다. 짧고 얕은 호흡보다 느리고 조절된 호흡을 하면 산소를 보존하고 불안을 줄이는 데 도움이 될 수 있습니다. 다이빙하는 동안 호흡 속도를 늦추는 것도 산소를 절약하고 이산화탄소 축적 위험을 줄이는 데 도움이 될 수 있습니다.

효과적인 호흡 기술의 핵심은 자신에게 가장 적합한 것을 찾고 정기적으로 연습하여 기술을 향상하고 다이빙하는 동안 자신감을 키우는 것임을 기억하십시오.

 

 

2. 휴식 기술

휴식은 프리 다이버에게 매우 중요한데, 산소를 절약하고 수중에서 공황이나 불안을 피하는 데 도움이 되기 때문입니다. 긴장을 풀면 심박수가 느려지고 몸이 산소를 더 효율적으로 사용합니다. 이것은 프리 다이빙에서 스쿠버 장비의 도움 없이 다이빙을 하고 공기 공급이 제한되기 때문에 중요합니다.

다음은 프리 다이버가 사용할 수 있는 이완 기술입니다.

- 호흡 운동: 

통제된 호흡은 몸을 진정시키고 스트레스를 줄이는 데 도움이 될 수 있습니다. 프리 다이버를 위한 일반적인 호흡 운동 중 하나는 "스퀘어 호흡"입니다. 이것은 천천히 그리고 깊게 숨을 들이쉬고, 정해진 시간 동안 숨을 참았다가, 천천히 내쉬고, 그런 다음 주기를 반복하기 전에 정해진 시간 동안 숨을 참는 것을 포함합니다.

 

- 점진적 근육 이완: 

이 기술은 신체의 여러 근육 그룹을 긴장시킨 다음 이완시키는 것을 포함합니다. 각 근육 그룹을 하나씩 이완시키는 데 집중함으로써 전반적인 긴장을 줄이고 마음을 진정시키는 데 도움을 줄 수 있습니다.

 

- 시각화: 

평화롭거나 차분한 장면을 시각화하면 수 중에서 스트레스와 불안을 줄이는 데 도움이 될 수 있습니다. 일부 프리 다이버는 잔잔한 바다에 떠 있는 자신을 상상하거나 잠수하면서 아름다운 수중 풍경을 상상할 수 있습니다.

 

- 마음 챙김: 

마음 챙김은 판단 없이 현재 순간에 주의를 집중하는 것과 관련된 명상의 한 형태입니다. 프리 다이버는 마음챙김을 사용하여 생각을 진정시키고 다이빙하는 동안 스트레스를 줄일 수 있습니다.

 

- 요가 및 기타 형태의 운동: 

규칙적인 신체 활동은 스트레스를 줄이고 전반적인 체력을 향상시키는 데 도움이 될 수 있습니다. 일부 프리 다이버는 다이빙을 위해 정신적으로나 육체적으로 준비하는 데 도움이 되는 요가나 다른 형태의 운동을 연습할 수 있습니다.


모든 사람이 이완 기법에 다르게 반응한다는 점을 기억하는 것이 중요하므로 자신에게 가장 적합한 방법을 찾으려면 약간의 시행착오가 필요할 수 있습니다. 핵심은 다이빙하는 동안 긴장을 풀고 집중하고 에너지를 절약하는 데 도움이 되는 기술을 찾는 것입니다.

 

 

3. 압력 평형

이퀄라이제이션은 안전하고 성공적인 프리 다이빙을 위한 중요한 기술입니다. 다이버가 하강하면 주변의 압력이 증가하여 적절하게 이퀄라이징되지 않으면 귀에 불편함이나 통증을 유발할 수 있습니다. 이퀄라이제이션은 증가하는 주변 물의 압력과 일치하도록 중이의 압력을 조정하는 것입니다.

프리 다이빙을 하는 동안 귀의 압력을 평형화하는 방법에는 여러 가지가 있습니다. 가장 일반적인 방법은 코를 막고 귀에 공기를 부드럽게 불어넣는 "발살바법"입니다. 이는 중이의 압력을 높이고 불편함이나 통증을 예방하는 데 도움이 됩니다.

압력 평형을 위한 또 다른 방법은 "Frenzel 기동"이라고 합니다. 이 방법은 코를 꼬집지 않고 혀의 뒤쪽을 사용하여 공기를 귀 안으로 밀어 넣는 것입니다. 이 기술은 Valsalva 기동을 사용하여 평형화하는 데 어려움이 있는 다이버 또는 코를 막을 수 있는 물에 많은 양의 입자상 물질이 있는 지역에서 다이빙하는 다이버에게 유용합니다.

하산 초기에 이퀄라이징을 시작하고 정기적으로 이퀄라이징하여 중이의 압력을 균등하게 유지하는 것이 중요합니다. 이퀄라이징에 어려움을 겪거나 귀에 불편함 또는 통증을 경험하는 다이버는 즉시 다이빙을 중단하고 수면 위로 올라와야 합니다.

이퀄라이제이션은 숙달하기 위해 연습과 반복이 필요한 기술이므로 프리 다이버가 시간을 들여 기술을 배우고 정기적으로 연습하여 자신감과 숙달을 구축하는 것이 중요합니다. 적절한 교육과 연습을 통해 평형화는 다이빙 경험의 자연스럽고 자동적인 부분이 될 수 있습니다.

 

 

4. 부력 조절

부력 조절은 프리 다이빙의 핵심 측면이며 안전하고 효율적인 다이빙에 필수적입니다. 다이버의 부력 또는 체중과 주변 물 사이의 균형을 조정하여 수중에서 자신의 위치를 제어하고 중성 또는 약간의 양성 부력을 유지할 수 있도록 합니다.

좋은 부력 제어를 통해 자유 다이버는 에너지를 절약하고 노력을 줄이며 공기 소비를 최소화할 수 있습니다. 또한 물 속에서 안전하고 안정적인 위치를 유지하고 해저 또는 기타 수중 위험 요소와의 접촉을 피하는 데 도움이 됩니다.

부력에 영향을 미치는 몇 가지 요인에는 폐의 공기량, 다이빙 장비의 무게, 착용하는 잠수복 또는 다이빙 스킨 등이 포함됩니다. 좋은 부력 조절을 유지하기 위해 프리 다이버는 폐 용적을 조절하고, 착용하고 있는 웨이트의 양을 조절하고, 수중에서 몸의 위치를 조절할 수 있어야 합니다.

부력 조절을 개선하기 위해 프리 다이버는 수면에 있는 동안 폐 용적 조절, 제어된 하강 및 상승 수행, 다양한 수준의 체중으로 수영과 같은 다양한 운동을 연습할 수 있습니다. 잠수복이나 잠수용 피부와 같은 적절한 단열 및 지지력을 제공하는 잘 맞는 장비를 사용하고 잠수 조건에 적합한 웨이트를 사용하는 것 또한 중요합니다.

결론적으로 부력 조절은 프리 다이빙의 중요한 측면이며 숙달하려면 연습과 반복이 필요합니다. 적절한 훈련과 연습을 통해 프리 다이버는 부력을 제어하고 노력을 최소화하며 안전하고 효율적인 다이빙 경험을 즐길 수 있는 기술과 자신감을 개발할 수 있습니다.

 

 

5. 안전 조치

프리다이빙은 스쿠버 장비나 기타 생명 유지 장치를 사용하지 않고 깊은 곳까지 잠수하는 스포츠이기 때문에 안전이 가장 중요합니다. 결과적으로 프리 다이빙과 관련된 위험을 최소화하기 위해 여러 가지 안전 조치를 취하는 것이 중요합니다.

- 파트너 다이빙: 

파트너와 함께하는 프리 다이빙은 수중에서 추가적인 안전과 지원을 제공하므로 강력하게 권장됩니다. 파트너는 다이버가 다이빙 중에 어려움을 겪을 경우 다이버가 수면으로 올라오도록 돕는 등 비상 시 도움을 제공할 수 있습니다.


- 다이빙 계획: 

매번 다이빙을 하기 전에 다이빙 장소, 수심, 수질 상태 및 다이버의 경험 수준과 같은 요소를 고려하여 다이빙을 신중하게 계획하는 것이 중요합니다. 이 정보는 잠수 수심, 지속 시간 및 필요할 수 있는 기타 안전 조치를 포함하여 적절한 잠수 계획을 결정하는 데 사용할 수 있습니다.

 

- 다이빙 플래그: 

인구 밀집 지역에서 다이빙할 때 다이버가 해당 지역에 있음을 보트 및 기타 선박에 알리기 위해 다이빙 플래그를 사용하는 것이 중요합니다. 이것은 충돌의 위험을 줄이는 데 도움이 되며 다른 보트와 선박에 대한 다이빙 사이트의 시각적 표시를 제공합니다.

 

- 표면 지지대: 

표면 지지대는 프리 다이빙에서 필수적인 안전 조치입니다. 이것은 보트, 플로트 또는 다른 지정된 표면 지원 시스템에 의해 제공될 수 있으며 필요한 경우 다이버가 도움을 요청할 수 있는 방법을 포함해야 합니다.

 

- 비상 장비: 

비상시 플로트, 신호 장치, 안전선과 같은 비상 장비를 즉시 사용할 수 있도록 하는 것이 중요합니다. 다이버는 또한 필요한 경우 도움을 청하기 위해 다이브 나이프와 휘파람을 휴대해야 합니다.

 

- 응급 처치 교육: 

모든 프리 다이버는 기본적인 응급 처치 교육을 받아야 하며 감압병 및 저체온증과 같은 일반적인 다이빙 관련 부상을 치료하는 절차에 익숙해야 합니다.


- 다이빙 보험: 

다이버는 다이빙 중 사고나 부상에 대비한 다이빙 보험 가입을 고려해야 합니다.

 

결론적으로 프리다이빙은 안전대책을 철저히 하고, 잠수하기 전에 모든 잠재적인 위험을 신중하게 고려하는 것이 필수적이다. 이러한 지침을 따르면 프리 다이버는 스포츠와 관련된 위험을 최소화하고 안전하고 즐거운 다이빙 경험을 즐길 수 있습니다.

 

 

반응형

'freedive' 카테고리의 다른 글

프리다이빙 장비 선택  (0) 2023.02.07
반응형

몫 double quotient = System.Math.Truncate(c); 

나머지 double remainder = a % b;

 

float 형으로 반환

반올림 Mathf.Round(3.5f) : 4f

올림 Mathf.Ceil(3.5f : 4f

내림 Mathf.Floor(3.5f) : 3f

 

int 형으로 반환

반올림 Mathf.RoundToInt(3.5f) : 4

올림 Mathf.CeilToInt(3.5f) : 4

내림 Mathf.FloorToInt(3.5f) : 3

 

<string to int 스트링에서 인트로 변환>

answer

Int = int.Parse(answer);

반응형

'unity C#' 카테고리의 다른 글

[Unity] 화면보호기 끄기  (0) 2023.01.30
[Unity] 중첩 코루틴 Coroutine  (0) 2023.01.30
[Unity] UI Blur Shader  (0) 2023.01.05
[Unity] VS Code Settings Sync  (0) 2022.12.26
[Unity] 암호화 복호화  (0) 2022.11.29
반응형
Screen.sleepTimeout = SleepTimeout.NeverSleep;

 

반응형

'unity C#' 카테고리의 다른 글

[Unity] float 반올림, 올림, 내림  (0) 2023.01.30
[Unity] 중첩 코루틴 Coroutine  (0) 2023.01.30
[Unity] UI Blur Shader  (0) 2023.01.05
[Unity] VS Code Settings Sync  (0) 2022.12.26
[Unity] 암호화 복호화  (0) 2022.11.29
반응형

중첩 코루틴

코루틴 내부에서 또다른 코루틴을 호출한다. 해당 코루틴이 완료될 때까지 기다리게 된다. 의존성이 있는 여러 작업을 수행하는데 유리하게 사용 될 수 있다.

 

void Start () {
	StartCoroutine (TestRoutine());
}

IEnumerator TestRoutine() {
	Debug.Log ("Run TestRoutine");
	yield return StartCoroutine (OtherRoutine ());
	Debug.Log ("Finish TestRoutine");
}

IEnumerator OtherRoutine() {
	Debug.Log ("Run OtherRoutine #1");
	yield return new WaitForSeconds (1.0f);
	Debug.Log ("Run OtherRoutine #2");
	yield return new WaitForSeconds (1.0f);
	Debug.Log ("Run OtherRoutine #3");
	yield return new WaitForSeconds (1.0f);
	Debug.Log ("Finish OtherRoutine");
}
반응형

'unity C#' 카테고리의 다른 글

[Unity] float 반올림, 올림, 내림  (0) 2023.01.30
[Unity] 화면보호기 끄기  (0) 2023.01.30
[Unity] UI Blur Shader  (0) 2023.01.05
[Unity] VS Code Settings Sync  (0) 2022.12.26
[Unity] 암호화 복호화  (0) 2022.11.29

+ Recent posts